From 037acfc182e36e9ddd07a2f885f755131cc0e37f Mon Sep 17 00:00:00 2001 From: Daniel Candia Flores Date: Tue, 24 Sep 2024 11:49:01 -0400 Subject: [PATCH 1/5] Add tests for bot/validations.js --- bot/validations.js | 20 +- package-lock.json | 40 ++ package.json | 3 +- tests/bot/validation.spec.js | 1119 ++++++++++++++++++++++++++++++++++ util/index.js | 11 + 5 files changed, 1182 insertions(+), 11 deletions(-) create mode 100644 tests/bot/validation.spec.js diff --git a/bot/validations.js b/bot/validations.js index 356d5447..be6ce69e 100644 --- a/bot/validations.js +++ b/bot/validations.js @@ -6,6 +6,7 @@ const { isIso4217, isDisputeSolver, removeLightningPrefix, + isOrderCreator, } = require('../util'); const { existLightningAddress } = require('../lnurl/lnurl-pay'); const { logger } = require('../logger'); @@ -82,6 +83,7 @@ const validateAdmin = async (ctx, id) => { const isSolver = isDisputeSolver(community, user); + //TODO this validation does not return anything if (!user.admin && !isSolver) return await messages.notAuthorized(ctx, tgUserId); @@ -169,6 +171,7 @@ const validateSellOrder = async ctx => { return false; } + // TODO, this validation could be amount > 0? if (amount !== 0 && amount < process.env.MIN_PAYMENT_AMT) { await messages.mustBeGreatherEqThan( ctx, @@ -319,7 +322,9 @@ const validateInvoice = async (ctx, lnInvoice) => { return false; } - if (new Date(invoice.expires_at) < latestDate) { + // This is a bug discovered by the test, new Date(invoice.expires_at) was an Object + // and latestDate is a string + if (new Date(invoice.expires_at).toISOString() < latestDate) { await messages.minimunExpirationTimeInvoiceMessage(ctx); return false; } @@ -361,7 +366,9 @@ const isValidInvoice = async (ctx, lnInvoice) => { }; } - if (new Date(invoice.expires_at) < latestDate) { + // This is a bug discovered by the test, new Date(invoice.expires_at) was an Object + // and latestDate is a string + if (new Date(invoice.expires_at).toISOString() < latestDate) { await messages.invoiceExpiryTooShortMessage(ctx); return { success: false, @@ -401,14 +408,7 @@ const isValidInvoice = async (ctx, lnInvoice) => { } }; -const isOrderCreator = (user, order) => { - try { - return user._id == order.creator_id; - } catch (error) { - logger.error(error); - return false; - } -}; + const validateTakeSellOrder = async (ctx, bot, user, order) => { try { diff --git a/package-lock.json b/package-lock.json index 139aee65..e716e777 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "mocha": "^9.2.2", "nodemon": "2.0.19", "prettier": "^2.6.2", + "proxyquire": "^2.1.3", "sinon": "^11.1.2", "telegram-test-api": "^2.5.0", "typescript": "^5.1.6" @@ -3613,6 +3614,19 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4318,6 +4332,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -5227,6 +5250,12 @@ "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, "node_modules/mongodb": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.1.tgz", @@ -5849,6 +5878,17 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, "node_modules/psbt": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/psbt/-/psbt-3.0.0.tgz", diff --git a/package.json b/package.json index b622708c..f60d71d1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "eslint .", "format": "prettier --write '**/*.js'", "pretest": "npx tsc", - "test": "export NODE_ENV=test && mocha --exit tests/" + "test": "export NODE_ENV=test && mocha --exit tests/**/*.spec.js" }, "license": "MIT", "dependencies": { @@ -53,6 +53,7 @@ "mocha": "^9.2.2", "nodemon": "2.0.19", "prettier": "^2.6.2", + "proxyquire": "^2.1.3", "sinon": "^11.1.2", "telegram-test-api": "^2.5.0", "typescript": "^5.1.6" diff --git a/tests/bot/validation.spec.js b/tests/bot/validation.spec.js new file mode 100644 index 00000000..bb2f14b5 --- /dev/null +++ b/tests/bot/validation.spec.js @@ -0,0 +1,1119 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const { ObjectId } = require('mongoose').Types; +const proxyquire = require('proxyquire'); + +const { + validateSellOrder, + validateBuyOrder, + validateInvoice, + isValidInvoice, + validateUser, + validateParams, + validateObjectId, + validateSuperAdmin, + validateAdmin, + validateTakeSellOrder, + validateUserWaitingOrder, + isBannedFromCommunity, +} = require('../../bot/validations'); +const messages = require('../../bot/messages'); +const { Order, User, Community } = require('../../models'); + +describe.only('Validations', () => { + let ctx; + let replyStub; + let sandbox; + let validations; + let existLightningAddressStub; + let isDisputeSolverStub; + let isOrderCreatorStub; + let bot, user, community; + + beforeEach(() => { + ctx = { + i18n: { + t: key => key, + locale: () => 'en', + }, + state: { + command: { + args: [], + }, + }, + update: { + callback_query: { + from: { + id: 1, + }, + }, + message: { + text: '', + from: { + id: 1, + }, + }, + }, + telegram: { + sendMessage: sinon.stub(), + }, + reply: () => { }, + state: { + command: { + args: [], + }, + }, + botInfo: { + username: 'testbot', + }, + }; + + sandbox = sinon.createSandbox(); + // Mock process.env within the sandbox + sandbox.stub(process, 'env').value({ + MIN_PAYMENT_AMT: 100, + NODE_ENV: 'production', + INVOICE_EXPIRATION_WINDOW: 3600000, + }); + + replyStub = sinon.stub(ctx, 'reply'); + existLightningAddressStub = sinon.stub(); + isDisputeSolverStub = sinon.stub(); + isOrderCreatorStub = sinon.stub(); + validations = proxyquire('../../bot/validations', { + '../lnurl/lnurl-pay': { + existLightningAddress: existLightningAddressStub, + }, + '../util': { + isDisputeSolver: isDisputeSolverStub, + isOrderCreator: isOrderCreatorStub, + } + }); + bot = { + telegram: { + sendMessage: sinon.stub(), + }, + }; + user = { + _id: new ObjectId(), + tg_id: 12345, + }; + }); + + afterEach(() => { + sinon.restore(); + sandbox.restore(); + }); + + describe('validateSellOrder', () => { + it('should return false if args length is less than 4', async () => { + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if amount is not a number', async () => { + ctx.state.command.args = ['test', '100', 'USD', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if fiat amount is not a number', async () => { + ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if fiat code is not valid, fiat code less than 3 characters', async () => { + ctx.state.command.args = ['10000', '100', 'US', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if fiat code is not valid, fiat code more than 5 characters', async () => { + ctx.state.command.args = ['10000', '100', 'USSDDD', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if amount is less than minimum', async () => { + ctx.state.command.args = ['1', '100', 'USD', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return object if validation success', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle']; + const result = await validateSellOrder(ctx); + expect(result).to.be.an('object'); + }); + + it('should return object if validation success with price margin', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle', '5']; + const result = await validateSellOrder(ctx); + expect(result).to.be.an('object'); + expect(result.priceMargin).to.equal('5'); + }); + + it('should return false if price margin is not a number', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle', 'test']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should work with ranges', async () => { + ctx.state.command.args = ['0', '100-200', 'USD', 'zelle', '5']; + const result = await validateSellOrder(ctx); + expect(result).to.be.an('object'); + expect(result.fiatAmount).to.deep.equal([100, 200]); + }); + + it('should fail with ranges with double dash', async () => { + ctx.state.command.args = ['0', '100--200', 'USD', 'zelle', '5']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + }); + + it('should fail with invalid ranges', async () => { + ctx.state.command.args = ['0', '200-100', 'USD', 'zelle', '5']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + }); + + it('should fail with ranges and amount', async () => { + ctx.state.command.args = ['1000', '100-200', 'USD', 'zelle', '5']; + const result = await validateSellOrder(ctx); + expect(result).to.equal(false); + }); + }); + + describe('validateBuyOrder', () => { + it('should return false if args length is less than 4', async () => { + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if amount is not a number', async () => { + ctx.state.command.args = ['test', '100', 'USD', 'zelle']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if fiat amount is not a number', async () => { + ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return false if amount is less than minimum', async () => { + ctx.state.command.args = ['1', '100', 'USD', 'zelle']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should return object if validation success', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle']; + const result = await validateBuyOrder(ctx); + expect(result).to.be.an('object'); + }); + + it('should return object if validation success with price margin', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle', '5']; + const result = await validateBuyOrder(ctx); + expect(result).to.be.an('object'); + expect(result.priceMargin).to.equal('5'); + }); + + it('should return false if price margin is not a number', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle', 'test']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + expect(replyStub.calledOnce).to.be.true; + }); + + it('should work with ranges', async () => { + ctx.state.command.args = ['0', '100-200', 'USD', 'zelle', '5']; + const result = await validateBuyOrder(ctx); + expect(result).to.be.an('object'); + expect(result.fiatAmount).to.deep.equal([100, 200]); + }); + + it('should fail with ranges with double dash', async () => { + ctx.state.command.args = ['0', '100--200', 'USD', 'zelle', '5']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + }); + + it('should fail with invalid ranges', async () => { + ctx.state.command.args = ['0', '200-100', 'USD', 'zelle', '5']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + }); + + it('should fail with ranges and amount', async () => { + ctx.state.command.args = ['1000', '100-200', 'USD', 'zelle', '5']; + const result = await validateBuyOrder(ctx); + expect(result).to.equal(false); + }); + }); + + describe('validateLightningAddress', () => { + it('should return true for a valid lightning address', async () => { + existLightningAddressStub.withArgs('test@ln.com').returns(true); + const result = await validations.validateLightningAddress('test@ln.com'); + expect(result).to.be.true; + }); + + it('should return false for an invalid lightning address (existence)', async () => { + existLightningAddressStub.withArgs('test@invalid.com').returns(false); + const result = await validations.validateLightningAddress('test@invalid.com'); + expect(result).to.be.false; + }); + }); + + describe('validateUser', () => { + it('should create a new user if it does not exist', async () => { + ctx.update.callback_query.from = { + username: 'testuser', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + }); + + sinon.stub(User, 'findOne').resolves(null); + sinon.stub(User.prototype, 'save').resolves(); + const user = await validateUser(ctx, true); + expect(user.save.calledOnce).to.be.true; + expect(user).to.be.an('object'); + expect(user.tg_id).to.be.equal(ctx.update.callback_query.from.id); + expect(user.username).to.be.equal(ctx.update.callback_query.from.username); + }); + + it('should return false if user does not exist and start is false', async () => { + const user = await validateUser(ctx, false); + expect(user).to.be.false; + }); + + it('should return the user if it exists', async () => { + ctx.update.callback_query.from = { + username: 'testuser', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + }); + + sinon.stub(User, 'findOne').resolves(newUser); + + const user = await validateUser(ctx, false); + expect(user).to.be.an('object'); + expect(user.tg_id).to.be.equal(newUser.tg_id); + expect(user.username).to.be.equal(newUser.username); + }); + + it('should update the username if it has changed', async () => { + ctx.update.callback_query.from = { + username: 'testuser-updated', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + }); + + sinon.stub(User, 'findOne').resolves(newUser); + + const user = await validateUser(ctx, false); + expect(user).to.be.an('object'); + expect(user.tg_id).to.be.equal(newUser.tg_id); + expect(user.username).to.be.equal('testuser-updated'); + }); + + it('should return false if the user is banned', async () => { + ctx.update.callback_query.from = { + username: 'testuser', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + banned: true, + }); + + sinon.stub(User, 'findOne').resolves(newUser); + + const user = await validateUser(ctx, false); + expect(user).to.be.false; + }); + }); + + describe('validateSuperAdmin', () => { + it('should return the user if it is a superadmin', async () => { + ctx.update.callback_query.from = { + username: 'testuser', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + admin: true, + }); + + sinon.stub(User, 'findOne').resolves(newUser); + + const user = await validateSuperAdmin(ctx); + expect(user).to.be.an('object'); + expect(user.tg_id).to.be.equal(newUser.tg_id); + expect(user.admin).to.be.true; + }); + + it('should return false if the user is not a superadmin', async () => { + ctx.update.callback_query.from = { + username: 'testuser', + id: '1' + }; + const newUser = new User({ + tg_id: ctx.update.callback_query.from.id, + username: ctx.update.callback_query.from.username, + admin: false, + }); + + sinon.stub(User, 'findOne').resolves(newUser); + + const user = await validateSuperAdmin(ctx); + expect(user).to.be.undefined; + }); + }); + + describe('validateAdmin', () => { + it('should return the user if it is an admin', async () => { + const newCommunity = new Community({ + name: 'testcommunity' + }); + const newUser = new User({ + tg_id: ctx.update.message.from.id, + username: ctx.update.message.from.username, + admin: true, + default_community_id: new ObjectId(newCommunity._id) + }); + + sinon.stub(User, 'findOne').resolves(newUser); + sinon.stub(Community, 'findOne').resolves(newCommunity); + isDisputeSolverStub.withArgs(newCommunity, newUser).returns(true); + const user = await validateAdmin(ctx); + expect(user).to.be.an('object'); + expect(user.tg_id).to.be.equal(newUser.tg_id); + expect(user.admin).to.be.true; + }); + + it('should return undefined if the user is not exist', async () => { + sinon.stub(User, 'findOne').resolves(null); + + const user = await validateAdmin(ctx); + expect(user).to.be.undefined; + }); + + it('should return undefined if the user is not dispute solver and it is not an admin', async () => { + const newCommunity = new Community({ + name: 'testcommunity' + }); + const newUser = new User({ + tg_id: ctx.update.message.from.id, + username: ctx.update.message.from.username, + admin: false, + default_community_id: new ObjectId(newCommunity._id) + }); + + sinon.stub(User, 'findOne').resolves(newUser); + sinon.stub(Community, 'findOne').resolves(newCommunity); + isDisputeSolverStub.withArgs(newCommunity, newUser).returns(false); + + const user = await validateAdmin(ctx); + expect(user).to.be.undefined; + }); + }); + + describe('validateSellOrder', () => { + it('should return false if the amount is not a number', async () => { + ctx.state.command.args = ['test', '100', 'USD', 'zelle']; + const order = await validateSellOrder(ctx); + expect(order).to.be.false; + }); + + it('should return false if the fiat amount is not a number', async () => { + ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; + const order = await validateSellOrder(ctx); + expect(order).to.be.false; + }); + + it('should return false if the fiat code is not valid', async () => { + ctx.state.command.args = ['10000', '100', 'invalid', 'zelle']; + const order = await validateSellOrder(ctx); + expect(order).to.be.false; + }); + + it('should return the order data if the order is valid', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle']; + const order = await validateSellOrder(ctx); + expect(order).to.be.an('object'); + expect(order.amount).to.be.equal(10000); + expect(order.fiatAmount).to.be.an('array').to.deep.equal([100]); + expect(order.fiatCode).to.be.equal('USD'); + expect(order.paymentMethod).to.be.equal('zelle'); + }); + }); + + describe('validateBuyOrder', () => { + it('should return false if the amount is not a number', async () => { + ctx.state.command.args = ['test', '100', 'USD', 'zelle']; + const order = await validateBuyOrder(ctx); + expect(order).to.be.false; + }); + + it('should return false if the fiat amount is not a number', async () => { + ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; + const order = await validateBuyOrder(ctx); + expect(order).to.be.false; + }); + + it('should return false if the fiat code is not valid', async () => { + ctx.state.command.args = ['10000', '100', 'invalid', 'zelle']; + const order = await validateBuyOrder(ctx); + expect(order).to.be.false; + }); + + it('should return the order data if the order is valid', async () => { + ctx.state.command.args = ['10000', '100', 'USD', 'zelle']; + const order = await validateBuyOrder(ctx); + expect(order).to.be.an('object'); + expect(order.amount).to.be.equal(10000); + expect(order.fiatAmount).to.be.an('array').to.deep.equal([100]); + expect(order.fiatCode).to.be.equal('USD'); + expect(order.paymentMethod).to.be.equal('zelle'); + }); + }); + + //TODO possible duplicated of isValidInvoice + describe('validateInvoice', () => { + // This test goes to the catch + it('should return false if the invoice is not valid', async () => { + const invoice = await validateInvoice(ctx, 'invalid-invoice'); + expect(invoice).to.be.false; + }); + + it('should return false if the invoice amount is too low', async () => { + const minPaymentAmount = 10; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: '2023-03-15T12:14:48.000Z' + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'minimunAmountInvoiceMessage'); + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + + expect(messages.minimunAmountInvoiceMessage.calledOnce).to.be.true; + expect(invoice).to.be.false; + }); + + it('should return false if the invoice is expired with an expired date', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: '2023-09-23T11:00:00.000Z' + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'minimunExpirationTimeInvoiceMessage'); + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + + expect(messages.minimunExpirationTimeInvoiceMessage.calledOnce).to.be.true; + expect(invoice).to.be.false; + }); + + it('should return false if the invoice is expired with is_expired true', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: true, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'expiredInvoiceMessage'); + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + + expect(messages.expiredInvoiceMessage.calledOnce).to.be.true; + expect(invoice).to.be.false; + }); + it('should return false if the invoice does not have a destination address', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + // destination: '03...', Missed destination address + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'requiredAddressInvoiceMessage'); + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + + expect(messages.requiredAddressInvoiceMessage.calledOnce).to.be.true; + expect(invoice).to.be.false; + }); + it('should return false if the invoice does not have an id', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + // id: '123', Missed Id + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'requiredHashInvoiceMessage'); + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + + expect(messages.requiredHashInvoiceMessage.calledOnce).to.be.true; + expect(invoice).to.be.false; + }); + it('should return the invoice if it is valid', async () => { + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: 20000, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { validateInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lnbc20m1p0t9k3gpp5kqum3c9qsp58yj2cp8gp5c9yqcnzen9s08jx2p4d7r97777g4qsq9qy9qsqgqqqqqqgqqqqqqgqjqdhnx9'; + + const invoice = await validateInvoice( + ctx, + lnInvoice + ); + expect(invoice).to.be.an('object'); + expect(invoice.network).to.be.equal('bc'); + expect(invoice.tokens).to.be.equal(20000); + }); + }); + + describe('isValidInvoice', () => { + // This goes to the catch + it('should return false if the invoice is not valid', async () => { + sinon.stub(messages, 'invoiceInvalidMessage'); + + const { success } = await isValidInvoice(ctx, 'invalid-invoice'); + expect(success).to.be.false; + expect(messages.invoiceInvalidMessage.calledOnce).to.be.true; + }); + + it('should return false if the invoice amount is too low', async () => { + const minPaymentAmount = 10; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: '2023-03-15T12:14:48.000Z' + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'invoiceMustBeLargerMessage'); + + const { success } = await isValidInvoice( + ctx, + lnInvoice + ); + + expect(messages.invoiceMustBeLargerMessage.calledOnce).to.be.true; + expect(success).to.be.false; + }); + + it('should return false if the invoice is expired with an expired date', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: '2023-09-23T11:00:00.000Z' + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'invoiceExpiryTooShortMessage'); + + const { success } = await isValidInvoice( + ctx, + lnInvoice + ); + + expect(messages.invoiceExpiryTooShortMessage.calledOnce).to.be.true; + expect(success).to.be.false; + }); + + it('should return false if the invoice is expired with is_expired true', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: true, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'invoiceHasExpiredMessage'); + + const { success } = await isValidInvoice( + ctx, + lnInvoice + ); + + expect(messages.invoiceHasExpiredMessage.calledOnce).to.be.true; + expect(success).to.be.false; + }); + it('should return false if the invoice does not have a destination address', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + // destination: '03...', Missed destination address + id: '123', + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'invoiceHasWrongDestinationMessage'); + + const { success } = await isValidInvoice( + ctx, + lnInvoice + ); + + expect(messages.invoiceHasWrongDestinationMessage.calledOnce).to.be.true; + expect(success).to.be.false; + }); + it('should return false if the invoice does not have an id', async () => { + const minPaymentAmount = 2000; + const resInvoice = { + network: 'bc', + destination: '03...', + // id: '123', Missed Id + tokens: minPaymentAmount, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lntb1u1p0g9j8ypqsp5xwyewd3a2v9hk6cakck29p29dj79wsdq6sty85c3qtgzq0w9qmmvpjlncqp58yj98ygg77skup0l293p7memz0hxq698j2zcqzpgxqyz5vqsp5uscy8wn5z9cqlcw78pd6skddj3sr8gzk9c9jxqyjw5d9q8wkdgg7z0xpvyjry9a9q6d8h829vdje7r29s5pjq7939v9kck2gsqgjg9'; + + sinon.stub(messages, 'requiredHashInvoiceMessage'); + + const { success } = await isValidInvoice( + ctx, + lnInvoice + ); + + expect(messages.requiredHashInvoiceMessage.calledOnce).to.be.true; + expect(success).to.be.false; + }); + it('should return the invoice if it is valid', async () => { + const resInvoice = { + network: 'bc', + destination: '03...', + id: '123', + tokens: 20000, + description: '', + expiry: 3600, + timestamp: 1678888888, + features: {}, + routes: [], + cltv_expiry: 9, + is_expired: false, + expires_at: new Date(Date.now() + 86400000).toISOString() // One day after + }; + const { isValidInvoice } = proxyquire('../../bot/validations', { + 'invoices': { + parsePaymentRequest: sinon.stub().returns(resInvoice), + }, + }); + + const lnInvoice = 'lnbc20m1p0t9k3gpp5kqum3c9qsp58yj2cp8gp5c9yqcnzen9s08jx2p4d7r97777g4qsq9qy9qsqgqqqqqqgqqqqqqgqjqdhnx9'; + + const { invoice } = await isValidInvoice( + ctx, + lnInvoice + ); + expect(invoice).to.be.an('object'); + expect(invoice.network).to.be.equal('bc'); + expect(invoice.tokens).to.be.equal(20000); + }); + }); + + describe('validateTakeSellOrder', () => { + it('should return false if the order does not exist', async () => { + const user = { _id: '1' }; + const order = null; + + sinon.stub(messages, 'invalidOrderMessage').resolves(); + + const isValid = await validateTakeSellOrder(ctx, {}, user, order); + + expect(messages.invalidOrderMessage.calledOnce).to.be.true; + expect(isValid).to.be.false; + }); + + it('should return false if the user is the order creator', async () => { + const user = { _id: '1' }; + const order = { + creator_id: '1', + type: 'sell', + status: 'PENDING', + }; + + sinon.stub(messages, 'cantTakeOwnOrderMessage').resolves(); + + const isValid = await validateTakeSellOrder(ctx, {}, user, order); + + expect(messages.cantTakeOwnOrderMessage.calledOnce).to.be.true; + expect(isValid).to.be.false; + }); + + it('should return false if the order type is not sell', async () => { + const user = { _id: '2' }; + const order = { + creator_id: '1', + type: 'buy', + status: 'PENDING', + }; + + sinon.stub(messages, 'invalidTypeOrderMessage').resolves(); + + const isValid = await validateTakeSellOrder(ctx, {}, user, order); + + expect(messages.invalidTypeOrderMessage.calledOnce).to.be.true; + expect(isValid).to.be.false; + }); + + it('should return false if the order status is not PENDING', async () => { + const user = { _id: '2' }; + const order = { + creator_id: '1', + type: 'sell', + status: 'ACTIVE', + }; + + sinon.stub(messages, 'alreadyTakenOrderMessage').resolves(); + + const isValid = await validateTakeSellOrder(ctx, {}, user, order); + + expect(messages.alreadyTakenOrderMessage.calledOnce).to.be.true; + expect(isValid).to.be.false; + }); + }); + + describe('validateParams', () => { + it('should return empty array if params length is not equal to paramNumber', async () => { + ctx.update.message.text = '/command param1 param2 param3'; + + sinon.stub(messages, 'customMessage').resolves(); + + const result = await validateParams(ctx, 3, 'errOutputString'); + expect(result).to.be.an('array').that.is.empty; + expect(messages.customMessage.calledOnce).to.be.true; + }); + + it('should return sliced array if params length is equal to paramNumber', async () => { + ctx.update.message.text = '/command param1 param2'; + const result = await validateParams(ctx, 3, 'errOutputString'); + expect(result).to.deep.equal(['param1', 'param2']); + expect(ctx.reply.notCalled).to.be.true; + }); + }); + + describe('validateObjectId', () => { + it('should return true if id is valid', async () => { + const validId = new ObjectId(); + const result = await validateObjectId(ctx, validId.toString()); + expect(result).to.be.true; + expect(ctx.reply.notCalled).to.be.true; + }); + + it('should return false if id is invalid', async () => { + const invalidId = 'invalidId'; + const result = await validateObjectId(ctx, invalidId); + expect(result).to.be.false; + expect(ctx.reply.calledOnce).to.be.true; + }); + }); + + describe('validateUserWaitingOrder', () => { + beforeEach(() => { + sinon.stub(Order, 'find'); + }); + + afterEach(() => { + Order.find.restore(); + }); + + it('should return true if user has no waiting orders', async () => { + Order.find.returns([]); + const result = await validateUserWaitingOrder(ctx, bot, user); + expect(result).to.be.true; + }); + + it('should return false and send message if user has a waiting sell order', async () => { + const order = { + _id: new ObjectId(), + seller_id: user._id, + status: 'WAITING_PAYMENT', + }; + Order.find.onCall(0).returns([order]); + Order.find.onCall(1).returns([]); + sinon.stub(messages, 'userCantTakeMoreThanOneWaitingOrderMessage').resolves(); + const result = await validateUserWaitingOrder(ctx, bot, user); + expect(result).to.be.false; + expect( + messages.userCantTakeMoreThanOneWaitingOrderMessage.calledOnce + ).to.be.true; + }); + + it('should return false and send message if user has a waiting buy order', async () => { + const order = { + _id: new ObjectId(), + buyer_id: user._id, + status: 'WAITING_BUYER_INVOICE', + }; + Order.find.onCall(0).returns([]); + Order.find.onCall(1).returns([order]); + sinon.stub(messages, 'userCantTakeMoreThanOneWaitingOrderMessage').resolves(); + const result = await validateUserWaitingOrder(ctx, bot, user); + expect(result).to.be.false; + expect( + messages.userCantTakeMoreThanOneWaitingOrderMessage.calledOnce + ).to.be.true; + }); + }); + + describe('isBannedFromCommunity', () => { + beforeEach(() => { + community = { + _id: new ObjectId(), + banned_users: [], + }; + sinon.stub(Community, 'findOne'); + }); + + afterEach(() => { + Community.findOne.restore(); + }); + + it('should return false if communityId is null', async () => { + const result = await isBannedFromCommunity(user, null); + expect(result).to.be.false; + }); + + it('should return false if community is not found', async () => { + Community.findOne.returns(null); + const result = await isBannedFromCommunity(user, community._id); + expect(result).to.be.false; + }); + + it('should return false if user is not banned', async () => { + Community.findOne.returns(community); + const result = await isBannedFromCommunity(user, community._id); + expect(result).to.be.false; + }); + + it('should return true if user is banned', async () => { + community.banned_users.push({ id: user._id }); + Community.findOne.returns(community); + const result = await isBannedFromCommunity(user, community._id); + expect(result).to.be.true; + }); + }); +}); diff --git a/util/index.js b/util/index.js index 0960768e..cde6641c 100644 --- a/util/index.js +++ b/util/index.js @@ -24,6 +24,17 @@ const isIso4217 = code => { exports.isIso4217 = isIso4217; +const isOrderCreator = (user, order) => { + try { + return user._id == order.creator_id; + } catch (error) { + logger.error(error); + return false; + } +}; + +exports.isOrderCreator = isOrderCreator; + const getCurrency = code => { if (!isIso4217(code)) return false; const currency = currencies[code]; From 8d469bef6dcced003ee3f38d1742d69a73b21e07 Mon Sep 17 00:00:00 2001 From: Daniel Candia Flores Date: Tue, 24 Sep 2024 17:39:24 -0400 Subject: [PATCH 2/5] Remove only --- tests/bot/validation.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bot/validation.spec.js b/tests/bot/validation.spec.js index bb2f14b5..b442e488 100644 --- a/tests/bot/validation.spec.js +++ b/tests/bot/validation.spec.js @@ -20,7 +20,7 @@ const { const messages = require('../../bot/messages'); const { Order, User, Community } = require('../../models'); -describe.only('Validations', () => { +describe('Validations', () => { let ctx; let replyStub; let sandbox; From eb9ff0edc7c809a61e63510a3aa28175bb523313 Mon Sep 17 00:00:00 2001 From: Daniel Candia Flores Date: Wed, 25 Sep 2024 16:01:07 -0400 Subject: [PATCH 3/5] Update bot tests and lightning tests --- tests/bot/bot.spec.js | 182 ++++++++++++++++++ tests/bot/mocks/currenciesResponse.js | 117 +++++++++++ tests/bot/mocks/languagesResponse.js | 33 ++++ tests/bot_test.js | 145 -------------- .../lightning.spec.js} | 10 +- tests/{ => ln/mocks}/lightningResponse.js | 4 +- tests/order.js | 28 --- tests/user.js | 16 -- 8 files changed, 339 insertions(+), 196 deletions(-) create mode 100644 tests/bot/bot.spec.js create mode 100644 tests/bot/mocks/currenciesResponse.js create mode 100644 tests/bot/mocks/languagesResponse.js delete mode 100644 tests/bot_test.js rename tests/{lightning_test.js => ln/lightning.spec.js} (63%) rename tests/{ => ln/mocks}/lightningResponse.js (87%) delete mode 100644 tests/order.js delete mode 100644 tests/user.js diff --git a/tests/bot/bot.spec.js b/tests/bot/bot.spec.js new file mode 100644 index 00000000..3aa57910 --- /dev/null +++ b/tests/bot/bot.spec.js @@ -0,0 +1,182 @@ +const path = require('path'); +const fs = require('fs'); +const sinon = require('sinon'); +const { expect } = require('chai'); +const { initialize } = require('../../bot'); +const { User, Order } = require('../../models'); +const ordersActions = require('../../bot/ordersActions'); +const { getCurrenciesWithPrice } = require('../../util'); +const { mockUpdatesResponseForCurrencies } = require('./mocks/currenciesResponse'); +const { mockUpdatesResponseForLanguages } = require('./mocks/languagesResponse'); + +describe('Telegram bot', () => { + const token = '123456'; + let server; + let sandbox; + let order, user; + + beforeEach(() => { + user = { + lang: 'ESP', + trades_completed: 7, + admin: false, + banned: false, + disputes: 0, + _id: '61006f0e85ad4f96cde94141', + balance: 0, + tg_id: '1', + username: 'negrunch', + created_at: '2021-07-27T20:39:42.403Z', + }; + order = { + buyer_dispute: false, + seller_dispute: false, + buyer_cooperativecancel: false, + seller_cooperativecancel: false, + _id: '612d451148df8353e387eeff', + description: + 'Vendiendo 111 sats\n' + + 'Por ves 111\n' + + 'Pago por Pagomovil\n' + + 'Tiene 7 operaciones exitosas', + amount: 111, + fee: 0.111, + creator_id: '61006f0e85ad4f96cde94141', + seller_id: '61006f0e85ad4f96cde94141', + type: 'sell', + status: 'PENDING', + fiat_amount: 111, + fiat_code: 'ves', + payment_method: 'Pagomovil', + tg_chat_id: '1', + tg_order_message: '1', + created_at: '2021-08-30T20:52:33.870Z', + }; + sandbox = sinon.createSandbox(); + // Mock process.env + sandbox.stub(process, 'env').value({ + CHANNEL: '@testChannel', + MIN_PAYMENT_AMT: 100, + NODE_ENV: 'test', + INVOICE_EXPIRATION_WINDOW: 3600000, + LND_GRPC_HOST: '127.0.0.1:10005', + }); + + // Mock TelegramServer + server = { + getClient: sandbox.stub().returns({ + makeCommand: sandbox.stub().returns({}), + sendCommand: sandbox.stub().resolves({ ok: true }), + getUpdates: sandbox.stub().resolves({ + ok: true, + result: [{ + update_id: 1, + message: { + message_id: 1, + from: { + id: 1, + is_bot: false, + first_name: 'Test', + username: 'testuser', + language_code: 'en', + }, + chat: { + id: 1, + first_name: 'Test', + username: 'testuser', + type: 'private', + }, + date: 1678888888, + text: '/start', + entities: [{ + offset: 0, + length: 6, + type: 'bot_command', + }], + }, + }], + }), + sendMessage: sandbox.stub().resolves({ ok: true }), + }), + ApiURL: 'http://localhost:9001', + }; + + // Mock mongo connection and models + sandbox.stub(User, 'findOne').resolves(user); + sandbox.stub(Order, 'findOne').resolves(null); + sandbox.stub(Order.prototype, 'save').resolves(order); + + // Initialize the bot + initialize(token, { telegram: { apiRoot: server.ApiURL } }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should start', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const command = client.makeCommand('/start'); + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + const updates = await client.getUpdates(); + expect(updates.ok).to.be.equal(true); + expect(updates.result.length).to.be.equal(1); + }); + + it('should return /sell help', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const command = client.makeCommand('/sell help'); + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + }); + + it('should create a /sell', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const createOrderStub = sandbox.stub(ordersActions, 'createOrder').returns(order); + const command = client.makeCommand('/sell 100 1 ves Pagomovil'); + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + }); + + it('should return /buy help', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const command = client.makeCommand('/buy help'); + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + }); + + it('should return the list of supported currencies', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const command = client.makeCommand('/listcurrencies'); + client.getUpdates.onCall(0).resolves(mockUpdatesResponseForCurrencies); + + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + const updates = await client.getUpdates(); + expect(updates.ok).to.be.equal(true); + expect( + (updates.result[0].message.text.match(/\n/g) || []).length - 1 + ).to.be.equal(getCurrenciesWithPrice().length); + }); + + it('should return flags of langs supported', async () => { + const client = server.getClient(token, { timeout: 5000 }); + const command = client.makeCommand('/setlang'); + client.getUpdates.onCall(0).resolves(mockUpdatesResponseForLanguages); + + const res = await client.sendCommand(command); + expect(res.ok).to.be.equal(true); + const updates = await client.getUpdates(); + expect(updates.ok).to.be.equal(true); + let flags = 0; + updates.result[0].message.reply_markup.inline_keyboard.forEach(flag => { + flags += flag.length; + }); + let langs = 0; + fs.readdirSync(path.join(__dirname, '../../locales')).forEach(file => { + langs++; + }); + expect(flags).to.be.equal(langs); + }); +}); diff --git a/tests/bot/mocks/currenciesResponse.js b/tests/bot/mocks/currenciesResponse.js new file mode 100644 index 00000000..9e5f3fb8 --- /dev/null +++ b/tests/bot/mocks/currenciesResponse.js @@ -0,0 +1,117 @@ +const mockUpdatesResponseForCurrencies = { + ok: true, + result: [ + { + time: 1726511559928, + botToken: '123456', + message: { + chat_id: 1, + text: 'Code | Name |\n' + + 'AED | United Arab Emirates Dirham | 🇦🇪\n' + + 'ANG | Netherlands Antillean Guilder | 🇧🇶\n' + + 'AOA | Angolan Kwanza | 🇦🇴\n' + + 'ARS | Peso argentino | 🇦🇷\n' + + 'AUD | Australian Dollar | 🇦🇺\n' + + 'AZN | Azerbaijani Manat | 🇦🇿\n' + + 'BDT | Bangladeshi Taka | 🇧🇩\n' + + 'BHD | Bahraini Dinar | 🇧🇭\n' + + 'BIF | Burundian Franc | 🇧🇮\n' + + 'BMD | Bermudan Dollar | 🇧🇲\n' + + 'BOB | Boliviano | 🇧🇴\n' + + 'BRL | Brazilian Real | 🇧🇷\n' + + 'BWP | Botswanan Pula | 🇧🇼\n' + + 'BYN | Belarusian Ruble | 🇧🇾\n' + + 'CAD | Canadian Dollar | 🇨🇦\n' + + 'CDF | Congolese Franc | 🇨🇩\n' + + 'CHF | Swiss Franc | 🇨🇭\n' + + 'CLP | Peso chileno | 🇨🇱\n' + + 'CNY | Chinese Yuan | 🇨🇳\n' + + 'COP | Peso colombiano | 🇨🇴\n' + + 'CRC | Colón | 🇨🇷\n' + + 'CUP | Peso cubano | 🇨🇺\n' + + 'CZK | Czech Republic Koruna | 🇨🇿\n' + + 'DJF | Djiboutian Franc | 🇩🇯\n' + + 'DKK | Danish Krone | 🇩🇰\n' + + 'DOP | Peso dominicano | 🇩🇴\n' + + 'DZD | Algerian Dinar | 🇩🇿\n' + + 'EGP | Egyptian Pound | 🇪🇬\n' + + 'ETB | Ethiopian Birr | 🇪🇹\n' + + 'EUR | Euro | 🇪🇺\n' + + 'GBP | British Pound Sterling | 🇬🇧\n' + + 'GEL | Georgian Lari | 🇬🇪\n' + + 'GHS | Ghanaian Cedi | 🇬🇭\n' + + 'GNF | Guinean Franc | 🇬🇳\n' + + 'GTQ | Quetzal | 🇬🇹\n' + + 'HKD | Hong Kong Dollar | 🇭🇰\n' + + 'HNL | Lempira | 🇭🇳\n' + + 'HUF | Hungarian Forint | 🇭🇺\n' + + 'IDR | Indonesian Rupiah | 🇮🇩\n' + + 'ILS | Israeli New Sheqel | 🇮🇱\n' + + 'INR | Indian Rupee | 🇮🇳\n' + + 'IRR | Iranian Rial | 🇮🇷\n' + + 'IRT | Iranian Tomen | 🇮🇷\n' + + 'JMD | Jamaican Dollar | 🇯🇲\n' + + 'JOD | Jordanian Dinar | 🇯🇴\n' + + 'JPY | Japanese Yen | 🇯🇵\n' + + 'KES | Kenyan Shilling | 🇰🇪\n' + + 'KGS | Kyrgystani Som | 🇰🇬\n' + + 'KRW | South Korean Won | 🇰🇷\n' + + 'KZT | Kazakhstani Tenge | 🇰🇿\n' + + 'LBP | Lebanese Pound | 🇱🇧\n' + + 'LKR | Sri Lankan Rupee | 🇱🇰\n' + + 'MAD | Moroccan Dirham | 🇲🇦\n' + + 'MGA | Malagasy Ariary | 🇲🇬\n' + + 'MLC | Moneda Libremente Convertible | 🇨🇺\n' + + 'MXN | Peso mexicano | 🇲🇽\n' + + 'MWK | Malawian Kwacha | 🇲🇼\n' + + 'MYR | Malaysian Ringgit | 🇲🇾\n' + + 'NAD | Namibian Dollar | 🇳🇦\n' + + 'NGN | Nigerian Naira | 🇳🇬\n' + + 'NIO | Nicaraguan Córdoba | 🇳🇮\n' + + 'NOK | Norwegian Krone | 🇳🇴\n' + + 'NPR | Nepalese Rupee | 🇳🇵\n' + + 'NZD | New Zealand Dollar | 🇳🇿\n' + + 'PAB | Panamanian Balboa | 🇵🇦\n' + + 'PEN | Peruvian Nuevo Sol | 🇵🇪\n' + + 'PHP | Philippine Peso | 🇵🇭\n' + + 'PKR | Pakistani Rupee | 🇵🇰\n' + + 'PLN | Polish Zloty | 🇵🇱\n' + + 'PYG | Paraguayan Guarani | 🇵🇾\n' + + 'QAR | Qatari Rial | 🇶🇦\n' + + 'RON | Romanian Leu | 🇷🇴\n' + + 'RSD | Serbian Dinar | 🇷🇸\n' + + 'RUB | руб | 🇷🇺\n' + + 'RWF | Rwandan Franc | 🇷🇼\n' + + 'SAR | Saudi Riyal | 🇸🇦\n' + + 'SEK | Swedish Krona | 🇸🇪\n' + + 'SGD | Singapore Dollar | 🇸🇬\n' + + 'THB | Thai Baht | 🇹🇭\n' + + 'TND | Tunisian Dinar | 🇹🇳\n' + + 'TRY | Turkish Lira | 🇹🇷\n' + + 'TTD | Trinidad and Tobago Dollar | 🇹🇹\n' + + 'TWD | New Taiwan Dollar | 🇹🇼\n' + + 'TZS | Tanzanian Shilling | 🇹🇿\n' + + 'UAH | Ukrainian Hryvnia | 🇺🇦\n' + + 'UGX | Ugandan Shilling | 🇺🇬\n' + + 'USD | US Dollar | 🇺🇸\n' + + 'USDSV | USD en El Salvador | 🇺🇸🇸🇻\n' + + 'USDVE | USD en Bs | 🇺🇸🇻🇪\n' + + 'USDUY | USD en Uruguay | 🇺🇸🇺🇾\n' + + 'UYU | Peso uruguayo | 🇺🇾\n' + + 'UZS | Uzbekistan Som | 🇺🇿\n' + + 'VES | Bolívar | 🇻🇪\n' + + 'VND | Vietnamese Dong | 🇻🇳\n' + + 'XAF | CFA Franc BEAC | 🇨🇲 🇨🇫 🇹🇩 🇬🇶 🇬🇦 🇨🇬\n' + + 'XOF | CFA Franc BCEAO | 🇧🇯 🇧🇫 🇨🇮 🇬🇼 🇲🇱 🇳🇪 🇸🇳 🇹🇬\n' + + 'ZAR | South African Rand | 🇿🇦\n' + }, + updateId: 2, + messageId: 2, + isRead: true + } + ] +}; + +module.exports = { + mockUpdatesResponseForCurrencies, +}; diff --git a/tests/bot/mocks/languagesResponse.js b/tests/bot/mocks/languagesResponse.js new file mode 100644 index 00000000..0e14b10f --- /dev/null +++ b/tests/bot/mocks/languagesResponse.js @@ -0,0 +1,33 @@ +const mockUpdatesResponseForLanguages = { + "ok": true, + "result": + [{ + "time": 1727274810291, + "botToken": "123456", + "message": { + "chat_id": 1, + "text": "Select language", + "reply_markup": + { + "inline_keyboard": + [ + [ + { + "text": "Русский 🇷🇺", + "callback_data": "setLanguage_ru" + }, + { + "text": "Українська 🇺🇦", + "callback_data": "setLanguage_uk" + } + ], + [ + { "text": "한글 🇰🇷", "callback_data": "setLanguage_ko" }, { "text": "Português 🇵🇹", "callback_data": "setLanguage_pt" }], [{ "text": "Français 🇫🇷", "callback_data": "setLanguage_fr" }, { "text": "Italiano 🇮🇹", "callback_data": "setLanguage_it" }], [{ "text": "Español 🇪🇸", "callback_data": "setLanguage_es" }, { "text": "فارسی 🇮🇷", "callback_data": "setLanguage_fa" }], [{ "text": "Deutsch 🇩🇪", "callback_data": "setLanguage_de" }, { "text": "English 🇬🇧", "callback_data": "setLanguage_en" }]] + } + }, "updateId": 2, "messageId": 2, "isRead": true + }] +}; + +module.exports = { + mockUpdatesResponseForLanguages, +}; \ No newline at end of file diff --git a/tests/bot_test.js b/tests/bot_test.js deleted file mode 100644 index 5a13934e..00000000 --- a/tests/bot_test.js +++ /dev/null @@ -1,145 +0,0 @@ -require('dotenv').config(); -const path = require('path'); -const fs = require('fs'); -const TelegramServer = require('telegram-test-api'); -const sinon = require('sinon'); -const { expect } = require('chai'); -const { initialize } = require('../bot'); -const { User, Order } = require('../models'); -const ordersActions = require('../bot/ordersActions'); -const testUser = require('./user'); -const testOrder = require('./order'); -const { getCurrenciesWithPrice } = require('../util'); -const { connect: mongoConnect } = require('../db_connect'); - -mongoConnect(); - -describe('Telegram bot test', () => { - const serverConfig = { port: 9001 }; - const token = '123456'; - let server; - beforeEach(() => { - server = new TelegramServer(serverConfig); - return server.start().then(() => { - // the options passed to Telegraf in this format will make it try to - // get messages from the server's local URL - const bot = initialize(token, { telegram: { apiRoot: server.ApiURL } }); - bot.startPolling(); - }); - }); - - afterEach(() => server.stop()); - - it('should start', async () => { - const client = server.getClient(token, { timeout: 5000 }); - const command = client.makeCommand('/start'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - const updates = await client.getUpdates(); - expect(updates.ok).to.be.equal(true); - expect(updates.result.length).to.be.equal(1); - }); - - it('should return /sell help', async () => { - const client = server.getClient(token, { timeout: 5000 }); - // We spy on the User findOne called on ValidateUser() - const userStub = sinon.stub(User, 'findOne'); - // We spy on the Order findOne called on validateSeller() - const orderStub = sinon.stub(Order, 'findOne'); - // We make it to return our data - userStub.returns(testUser); - orderStub.returns(false); - const command = client.makeCommand('/sell help'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - // const updates = await client.getUpdates(); - // We restore the stubs - userStub.restore(); - orderStub.restore(); - // expect(updates.ok).to.be.equal(true); - // TODO: we will check the message with the text from i18n - // expect(updates.result[0].message.text).to.be.equal("/sell \\<_monto en sats_\\> \\<_monto en fiat_\\> \\<_código fiat_\\> \\<_método de pago_\\> \\[_prima/descuento_\\]"); - }); - - it('should create a /sell', async () => { - const client = server.getClient(token, { timeout: 5000 }); - // We spy on the User findOne called on ValidateUser() - const userStub = sinon.stub(User, 'findOne'); - // We spy on the Order findOne called on validateSeller() - const orderStub = sinon.stub(Order, 'findOne'); - // We spy the createOrder call - const createOrderStub = sinon.stub(ordersActions, 'createOrder'); - // We make it to return our data - userStub.returns(testUser); - orderStub.returns(false); - createOrderStub.returns(testOrder); - const command = client.makeCommand('/sell 100 1 ves Pagomovil'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - // const updates = await client.getUpdates(); - // We restore the stubs - userStub.restore(); - orderStub.restore(); - createOrderStub.restore(); - // expect(updates.ok).to.be.equal(true); - // expect(updates.result.length).to.be.equal(3); - // expect(updates.result[0].message.chat_id).to.be.equal(process.env.CHANNEL); - }); - - it('should return /buy help', async () => { - const client = server.getClient(token, { timeout: 5000 }); - // We spy on the User findOne called on ValidateUser() - const userStub = sinon.stub(User, 'findOne'); - // We spy on the Order findOne called on validateSeller() - const orderStub = sinon.stub(Order, 'findOne'); - // We make it to return our data - userStub.returns(testUser); - orderStub.returns(false); - const command = client.makeCommand('/buy help'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - // const updates = await client.getUpdates(); - // We restore the stubs - userStub.restore(); - orderStub.restore(); - // expect(updates.ok).to.be.equal(true); - // TODO: we will check the message with the text from i18n - // expect(updates.result[0].message.text).to.be.equal("/buy \\<_monto en sats_\\> \\<_monto en fiat_\\> \\<_código fiat_\\> \\<_método de pago_\\> \\[_prima/descuento_\\]"); - }); - - it('should return the list of supported currencies', async () => { - const client = server.getClient(token, { timeout: 5000 }); - const userStub = sinon.stub(User, 'findOne'); - userStub.returns(testUser); - const command = client.makeCommand('/listcurrencies'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - const updates = await client.getUpdates(); - userStub.restore(); - expect(updates.ok).to.be.equal(true); - expect( - (updates.result[0].message.text.match(/\n/g) || []).length - 1 - ).to.be.equal(getCurrenciesWithPrice().length); - }); - - it('should return flags of langs supported', async () => { - const client = server.getClient(token, { timeout: 5000 }); - const userStub = sinon.stub(User, 'findOne'); - userStub.returns(testUser); - const command = client.makeCommand('/setlang'); - const res = await client.sendCommand(command); - expect(res.ok).to.be.equal(true); - const updates = await client.getUpdates(); - userStub.restore(); - expect(updates.ok).to.be.equal(true); - let flags = 0; - updates.result[0].message.reply_markup.inline_keyboard.forEach(flag => { - flags += flag.length; - }); - let langs = 0; - fs.readdirSync(path.join(__dirname, '../locales')).forEach(file => { - langs++; - }); - expect(flags).to.be.equal(langs); - }); -}); diff --git a/tests/lightning_test.js b/tests/ln/lightning.spec.js similarity index 63% rename from tests/lightning_test.js rename to tests/ln/lightning.spec.js index 2ce2ffb5..76708b6e 100644 --- a/tests/lightning_test.js +++ b/tests/ln/lightning.spec.js @@ -2,22 +2,22 @@ const sinon = require('sinon'); const { expect } = require('chai'); const lightning = require('lightning'); const { parsePaymentRequest } = require('invoices'); -const { createHodlInvoiceResponse } = require('./lightningResponse'); -const { createHoldInvoice } = require('../ln'); +const { mockCreateHodlResponseForLightning } = require('./mocks/lightningResponse'); +const { createHoldInvoice } = require('../../ln'); describe('Testing lighting network', () => { it('Should create hold invoice', async () => { // We spy on the lighting service call const stub = sinon.stub(lightning, 'createHodlInvoice'); // Then we test our internal lightning call - stub.returns(createHodlInvoiceResponse); + stub.returns(mockCreateHodlResponseForLightning); const { hash, request } = await createHoldInvoice({ description: 'Holis', amount: 200, }); const invoice = parsePaymentRequest({ request }); - expect(hash).to.be.equal(createHodlInvoiceResponse.id); - expect(invoice.tokens).to.be.equal(createHodlInvoiceResponse.tokens); + expect(hash).to.be.equal(mockCreateHodlResponseForLightning.id); + expect(invoice.tokens).to.be.equal(mockCreateHodlResponseForLightning.tokens); }); }); diff --git a/tests/lightningResponse.js b/tests/ln/mocks/lightningResponse.js similarity index 87% rename from tests/lightningResponse.js rename to tests/ln/mocks/lightningResponse.js index e9f0db5e..98d2c20f 100644 --- a/tests/lightningResponse.js +++ b/tests/ln/mocks/lightningResponse.js @@ -1,4 +1,4 @@ -const createHodlInvoiceResponse = { +const mockCreateHodlResponseForLightning = { chain_address: undefined, created_at: '2021-08-09T17:32:02.000Z', description: 'Venta por @P2PLNBot', @@ -11,5 +11,5 @@ const createHodlInvoiceResponse = { }; module.exports = { - createHodlInvoiceResponse, + mockCreateHodlResponseForLightning, }; diff --git a/tests/order.js b/tests/order.js deleted file mode 100644 index 1970bef0..00000000 --- a/tests/order.js +++ /dev/null @@ -1,28 +0,0 @@ -const order = { - buyer_dispute: false, - seller_dispute: false, - buyer_cooperativecancel: false, - seller_cooperativecancel: false, - _id: '612d451148df8353e387eeff', - description: - 'Vendiendo 111 sats\n' + - 'Por ves 111\n' + - 'Pago por Pagomovil\n' + - 'Tiene 7 operaciones exitosas', - amount: 111, - fee: 0.111, - creator_id: '61006f0e85ad4f96cde94141', - seller_id: '61006f0e85ad4f96cde94141', - type: 'sell', - status: 'PENDING', - fiat_amount: 111, - fiat_code: 'ves', - payment_method: 'Pagomovil', - tg_chat_id: '1', - tg_order_message: '1', - created_at: '2021-08-30T20:52:33.870Z', -}; - -order.save = () => 'saved'; - -module.exports = order; diff --git a/tests/user.js b/tests/user.js deleted file mode 100644 index 0c6fef0d..00000000 --- a/tests/user.js +++ /dev/null @@ -1,16 +0,0 @@ -const user = { - lang: 'ESP', - trades_completed: 7, - admin: false, - banned: false, - disputes: 0, - _id: '61006f0e85ad4f96cde94141', - balance: 0, - tg_id: '1', - username: 'negrunch', - created_at: '2021-07-27T20:39:42.403Z', -}; - -user.save = () => {}; - -module.exports = user; From d2d8ddd383253a6151d04b10ae8ea1da9d8aaa6b Mon Sep 17 00:00:00 2001 From: Daniel Candia Flores Date: Wed, 25 Sep 2024 16:21:24 -0400 Subject: [PATCH 4/5] Update describe name --- tests/ln/lightning.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ln/lightning.spec.js b/tests/ln/lightning.spec.js index 76708b6e..1299805c 100644 --- a/tests/ln/lightning.spec.js +++ b/tests/ln/lightning.spec.js @@ -5,7 +5,7 @@ const { parsePaymentRequest } = require('invoices'); const { mockCreateHodlResponseForLightning } = require('./mocks/lightningResponse'); const { createHoldInvoice } = require('../../ln'); -describe('Testing lighting network', () => { +describe('Lighting network', () => { it('Should create hold invoice', async () => { // We spy on the lighting service call const stub = sinon.stub(lightning, 'createHodlInvoice'); From 8dccdd9c8c0b8a255540f0fe9f32f13cce2e4870 Mon Sep 17 00:00:00 2001 From: Daniel Candia Flores Date: Wed, 25 Sep 2024 17:34:25 -0400 Subject: [PATCH 5/5] Update files to pass the linter check --- bot/validations.js | 2 +- tests/bot/bot.spec.js | 2 - tests/bot/validation.spec.js | 163 +++++++++++++++++------------------ 3 files changed, 78 insertions(+), 89 deletions(-) diff --git a/bot/validations.js b/bot/validations.js index be6ce69e..6264d637 100644 --- a/bot/validations.js +++ b/bot/validations.js @@ -83,7 +83,7 @@ const validateAdmin = async (ctx, id) => { const isSolver = isDisputeSolver(community, user); - //TODO this validation does not return anything + // TODO this validation does not return anything if (!user.admin && !isSolver) return await messages.notAuthorized(ctx, tgUserId); diff --git a/tests/bot/bot.spec.js b/tests/bot/bot.spec.js index 3aa57910..bf7e56b6 100644 --- a/tests/bot/bot.spec.js +++ b/tests/bot/bot.spec.js @@ -4,7 +4,6 @@ const sinon = require('sinon'); const { expect } = require('chai'); const { initialize } = require('../../bot'); const { User, Order } = require('../../models'); -const ordersActions = require('../../bot/ordersActions'); const { getCurrenciesWithPrice } = require('../../util'); const { mockUpdatesResponseForCurrencies } = require('./mocks/currenciesResponse'); const { mockUpdatesResponseForLanguages } = require('./mocks/languagesResponse'); @@ -133,7 +132,6 @@ describe('Telegram bot', () => { it('should create a /sell', async () => { const client = server.getClient(token, { timeout: 5000 }); - const createOrderStub = sandbox.stub(ordersActions, 'createOrder').returns(order); const command = client.makeCommand('/sell 100 1 ves Pagomovil'); const res = await client.sendCommand(command); expect(res.ok).to.be.equal(true); diff --git a/tests/bot/validation.spec.js b/tests/bot/validation.spec.js index b442e488..b8d2dd57 100644 --- a/tests/bot/validation.spec.js +++ b/tests/bot/validation.spec.js @@ -58,11 +58,6 @@ describe('Validations', () => { sendMessage: sinon.stub(), }, reply: () => { }, - state: { - command: { - args: [], - }, - }, botInfo: { username: 'testbot', }, @@ -109,42 +104,42 @@ describe('Validations', () => { it('should return false if args length is less than 4', async () => { const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return false if amount is not a number', async () => { ctx.state.command.args = ['test', '100', 'USD', 'zelle']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return false if fiat amount is not a number', async () => { ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return false if fiat code is not valid, fiat code less than 3 characters', async () => { ctx.state.command.args = ['10000', '100', 'US', 'zelle']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return false if fiat code is not valid, fiat code more than 5 characters', async () => { ctx.state.command.args = ['10000', '100', 'USSDDD', 'zelle']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return false if amount is less than minimum', async () => { ctx.state.command.args = ['1', '100', 'USD', 'zelle']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.equal(true); }); it('should return object if validation success', async () => { @@ -164,7 +159,7 @@ describe('Validations', () => { ctx.state.command.args = ['10000', '100', 'USD', 'zelle', 'test']; const result = await validateSellOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should work with ranges', async () => { @@ -197,28 +192,28 @@ describe('Validations', () => { it('should return false if args length is less than 4', async () => { const result = await validateBuyOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should return false if amount is not a number', async () => { ctx.state.command.args = ['test', '100', 'USD', 'zelle']; const result = await validateBuyOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should return false if fiat amount is not a number', async () => { ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; const result = await validateBuyOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should return false if amount is less than minimum', async () => { ctx.state.command.args = ['1', '100', 'USD', 'zelle']; const result = await validateBuyOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should return object if validation success', async () => { @@ -238,7 +233,7 @@ describe('Validations', () => { ctx.state.command.args = ['10000', '100', 'USD', 'zelle', 'test']; const result = await validateBuyOrder(ctx); expect(result).to.equal(false); - expect(replyStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.equal(true); }); it('should work with ranges', async () => { @@ -271,13 +266,13 @@ describe('Validations', () => { it('should return true for a valid lightning address', async () => { existLightningAddressStub.withArgs('test@ln.com').returns(true); const result = await validations.validateLightningAddress('test@ln.com'); - expect(result).to.be.true; + expect(result).to.equal(true); }); it('should return false for an invalid lightning address (existence)', async () => { existLightningAddressStub.withArgs('test@invalid.com').returns(false); const result = await validations.validateLightningAddress('test@invalid.com'); - expect(result).to.be.false; + expect(result).to.equal(false); }); }); @@ -287,15 +282,11 @@ describe('Validations', () => { username: 'testuser', id: '1' }; - const newUser = new User({ - tg_id: ctx.update.callback_query.from.id, - username: ctx.update.callback_query.from.username, - }); - + sinon.stub(User, 'findOne').resolves(null); sinon.stub(User.prototype, 'save').resolves(); const user = await validateUser(ctx, true); - expect(user.save.calledOnce).to.be.true; + expect(user.save.calledOnce).to.equal(true); expect(user).to.be.an('object'); expect(user.tg_id).to.be.equal(ctx.update.callback_query.from.id); expect(user.username).to.be.equal(ctx.update.callback_query.from.username); @@ -303,7 +294,7 @@ describe('Validations', () => { it('should return false if user does not exist and start is false', async () => { const user = await validateUser(ctx, false); - expect(user).to.be.false; + expect(user).to.equal(false); }); it('should return the user if it exists', async () => { @@ -356,7 +347,7 @@ describe('Validations', () => { sinon.stub(User, 'findOne').resolves(newUser); const user = await validateUser(ctx, false); - expect(user).to.be.false; + expect(user).to.equal(false); }); }); @@ -377,7 +368,7 @@ describe('Validations', () => { const user = await validateSuperAdmin(ctx); expect(user).to.be.an('object'); expect(user.tg_id).to.be.equal(newUser.tg_id); - expect(user.admin).to.be.true; + expect(user.admin).to.equal(true); }); it('should return false if the user is not a superadmin', async () => { @@ -394,7 +385,7 @@ describe('Validations', () => { sinon.stub(User, 'findOne').resolves(newUser); const user = await validateSuperAdmin(ctx); - expect(user).to.be.undefined; + expect(user).to.equal(undefined); }); }); @@ -416,14 +407,14 @@ describe('Validations', () => { const user = await validateAdmin(ctx); expect(user).to.be.an('object'); expect(user.tg_id).to.be.equal(newUser.tg_id); - expect(user.admin).to.be.true; + expect(user.admin).to.equal(true); }); it('should return undefined if the user is not exist', async () => { sinon.stub(User, 'findOne').resolves(null); const user = await validateAdmin(ctx); - expect(user).to.be.undefined; + expect(user).to.equal(undefined); }); it('should return undefined if the user is not dispute solver and it is not an admin', async () => { @@ -442,7 +433,7 @@ describe('Validations', () => { isDisputeSolverStub.withArgs(newCommunity, newUser).returns(false); const user = await validateAdmin(ctx); - expect(user).to.be.undefined; + expect(user).to.equal(undefined); }); }); @@ -450,19 +441,19 @@ describe('Validations', () => { it('should return false if the amount is not a number', async () => { ctx.state.command.args = ['test', '100', 'USD', 'zelle']; const order = await validateSellOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return false if the fiat amount is not a number', async () => { ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; const order = await validateSellOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return false if the fiat code is not valid', async () => { ctx.state.command.args = ['10000', '100', 'invalid', 'zelle']; const order = await validateSellOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return the order data if the order is valid', async () => { @@ -480,19 +471,19 @@ describe('Validations', () => { it('should return false if the amount is not a number', async () => { ctx.state.command.args = ['test', '100', 'USD', 'zelle']; const order = await validateBuyOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return false if the fiat amount is not a number', async () => { ctx.state.command.args = ['10000', 'test', 'USD', 'zelle']; const order = await validateBuyOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return false if the fiat code is not valid', async () => { ctx.state.command.args = ['10000', '100', 'invalid', 'zelle']; const order = await validateBuyOrder(ctx); - expect(order).to.be.false; + expect(order).to.equal(false); }); it('should return the order data if the order is valid', async () => { @@ -506,12 +497,12 @@ describe('Validations', () => { }); }); - //TODO possible duplicated of isValidInvoice + // TODO possible duplicated of isValidInvoice describe('validateInvoice', () => { // This test goes to the catch it('should return false if the invoice is not valid', async () => { const invoice = await validateInvoice(ctx, 'invalid-invoice'); - expect(invoice).to.be.false; + expect(invoice).to.equal(false); }); it('should return false if the invoice amount is too low', async () => { @@ -545,8 +536,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.minimunAmountInvoiceMessage.calledOnce).to.be.true; - expect(invoice).to.be.false; + expect(messages.minimunAmountInvoiceMessage.calledOnce).to.equal(true); + expect(invoice).to.equal(false); }); it('should return false if the invoice is expired with an expired date', async () => { @@ -580,8 +571,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.minimunExpirationTimeInvoiceMessage.calledOnce).to.be.true; - expect(invoice).to.be.false; + expect(messages.minimunExpirationTimeInvoiceMessage.calledOnce).to.equal(true); + expect(invoice).to.equal(false); }); it('should return false if the invoice is expired with is_expired true', async () => { @@ -615,8 +606,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.expiredInvoiceMessage.calledOnce).to.be.true; - expect(invoice).to.be.false; + expect(messages.expiredInvoiceMessage.calledOnce).to.equal(true); + expect(invoice).to.equal(false); }); it('should return false if the invoice does not have a destination address', async () => { const minPaymentAmount = 2000; @@ -649,8 +640,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.requiredAddressInvoiceMessage.calledOnce).to.be.true; - expect(invoice).to.be.false; + expect(messages.requiredAddressInvoiceMessage.calledOnce).to.equal(true); + expect(invoice).to.equal(false); }); it('should return false if the invoice does not have an id', async () => { const minPaymentAmount = 2000; @@ -683,8 +674,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.requiredHashInvoiceMessage.calledOnce).to.be.true; - expect(invoice).to.be.false; + expect(messages.requiredHashInvoiceMessage.calledOnce).to.equal(true); + expect(invoice).to.equal(false); }); it('should return the invoice if it is valid', async () => { const resInvoice = { @@ -725,8 +716,8 @@ describe('Validations', () => { sinon.stub(messages, 'invoiceInvalidMessage'); const { success } = await isValidInvoice(ctx, 'invalid-invoice'); - expect(success).to.be.false; - expect(messages.invoiceInvalidMessage.calledOnce).to.be.true; + expect(success).to.equal(false); + expect(messages.invoiceInvalidMessage.calledOnce).to.equal(true); }); it('should return false if the invoice amount is too low', async () => { @@ -760,8 +751,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.invoiceMustBeLargerMessage.calledOnce).to.be.true; - expect(success).to.be.false; + expect(messages.invoiceMustBeLargerMessage.calledOnce).to.equal(true); + expect(success).to.equal(false); }); it('should return false if the invoice is expired with an expired date', async () => { @@ -795,8 +786,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.invoiceExpiryTooShortMessage.calledOnce).to.be.true; - expect(success).to.be.false; + expect(messages.invoiceExpiryTooShortMessage.calledOnce).to.equal(true); + expect(success).to.equal(false); }); it('should return false if the invoice is expired with is_expired true', async () => { @@ -830,8 +821,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.invoiceHasExpiredMessage.calledOnce).to.be.true; - expect(success).to.be.false; + expect(messages.invoiceHasExpiredMessage.calledOnce).to.equal(true); + expect(success).to.equal(false); }); it('should return false if the invoice does not have a destination address', async () => { const minPaymentAmount = 2000; @@ -864,8 +855,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.invoiceHasWrongDestinationMessage.calledOnce).to.be.true; - expect(success).to.be.false; + expect(messages.invoiceHasWrongDestinationMessage.calledOnce).to.equal(true); + expect(success).to.equal(false); }); it('should return false if the invoice does not have an id', async () => { const minPaymentAmount = 2000; @@ -898,8 +889,8 @@ describe('Validations', () => { lnInvoice ); - expect(messages.requiredHashInvoiceMessage.calledOnce).to.be.true; - expect(success).to.be.false; + expect(messages.requiredHashInvoiceMessage.calledOnce).to.equal(true); + expect(success).to.equal(false); }); it('should return the invoice if it is valid', async () => { const resInvoice = { @@ -943,8 +934,8 @@ describe('Validations', () => { const isValid = await validateTakeSellOrder(ctx, {}, user, order); - expect(messages.invalidOrderMessage.calledOnce).to.be.true; - expect(isValid).to.be.false; + expect(messages.invalidOrderMessage.calledOnce).to.equal(true); + expect(isValid).to.equal(false); }); it('should return false if the user is the order creator', async () => { @@ -959,8 +950,8 @@ describe('Validations', () => { const isValid = await validateTakeSellOrder(ctx, {}, user, order); - expect(messages.cantTakeOwnOrderMessage.calledOnce).to.be.true; - expect(isValid).to.be.false; + expect(messages.cantTakeOwnOrderMessage.calledOnce).to.equal(true); + expect(isValid).to.equal(false); }); it('should return false if the order type is not sell', async () => { @@ -975,8 +966,8 @@ describe('Validations', () => { const isValid = await validateTakeSellOrder(ctx, {}, user, order); - expect(messages.invalidTypeOrderMessage.calledOnce).to.be.true; - expect(isValid).to.be.false; + expect(messages.invalidTypeOrderMessage.calledOnce).to.equal(true); + expect(isValid).to.equal(false); }); it('should return false if the order status is not PENDING', async () => { @@ -991,8 +982,8 @@ describe('Validations', () => { const isValid = await validateTakeSellOrder(ctx, {}, user, order); - expect(messages.alreadyTakenOrderMessage.calledOnce).to.be.true; - expect(isValid).to.be.false; + expect(messages.alreadyTakenOrderMessage.calledOnce).to.equal(true); + expect(isValid).to.equal(false); }); }); @@ -1003,15 +994,15 @@ describe('Validations', () => { sinon.stub(messages, 'customMessage').resolves(); const result = await validateParams(ctx, 3, 'errOutputString'); - expect(result).to.be.an('array').that.is.empty; - expect(messages.customMessage.calledOnce).to.be.true; + expect(result).to.be.an('array').to.have.lengthOf(0); + expect(messages.customMessage.calledOnce).to.equal(true); }); it('should return sliced array if params length is equal to paramNumber', async () => { ctx.update.message.text = '/command param1 param2'; const result = await validateParams(ctx, 3, 'errOutputString'); expect(result).to.deep.equal(['param1', 'param2']); - expect(ctx.reply.notCalled).to.be.true; + expect(ctx.reply.notCalled).to.equal(true); }); }); @@ -1019,15 +1010,15 @@ describe('Validations', () => { it('should return true if id is valid', async () => { const validId = new ObjectId(); const result = await validateObjectId(ctx, validId.toString()); - expect(result).to.be.true; - expect(ctx.reply.notCalled).to.be.true; + expect(result).to.equal(true); + expect(ctx.reply.notCalled).to.equal(true); }); it('should return false if id is invalid', async () => { const invalidId = 'invalidId'; const result = await validateObjectId(ctx, invalidId); - expect(result).to.be.false; - expect(ctx.reply.calledOnce).to.be.true; + expect(result).to.equal(false); + expect(ctx.reply.calledOnce).to.equal(true); }); }); @@ -1043,7 +1034,7 @@ describe('Validations', () => { it('should return true if user has no waiting orders', async () => { Order.find.returns([]); const result = await validateUserWaitingOrder(ctx, bot, user); - expect(result).to.be.true; + expect(result).to.equal(true); }); it('should return false and send message if user has a waiting sell order', async () => { @@ -1056,10 +1047,10 @@ describe('Validations', () => { Order.find.onCall(1).returns([]); sinon.stub(messages, 'userCantTakeMoreThanOneWaitingOrderMessage').resolves(); const result = await validateUserWaitingOrder(ctx, bot, user); - expect(result).to.be.false; + expect(result).to.equal(false); expect( messages.userCantTakeMoreThanOneWaitingOrderMessage.calledOnce - ).to.be.true; + ).to.equal(true); }); it('should return false and send message if user has a waiting buy order', async () => { @@ -1072,10 +1063,10 @@ describe('Validations', () => { Order.find.onCall(1).returns([order]); sinon.stub(messages, 'userCantTakeMoreThanOneWaitingOrderMessage').resolves(); const result = await validateUserWaitingOrder(ctx, bot, user); - expect(result).to.be.false; + expect(result).to.equal(false); expect( messages.userCantTakeMoreThanOneWaitingOrderMessage.calledOnce - ).to.be.true; + ).to.equal(true); }); }); @@ -1094,26 +1085,26 @@ describe('Validations', () => { it('should return false if communityId is null', async () => { const result = await isBannedFromCommunity(user, null); - expect(result).to.be.false; + expect(result).to.equal(false); }); it('should return false if community is not found', async () => { Community.findOne.returns(null); const result = await isBannedFromCommunity(user, community._id); - expect(result).to.be.false; + expect(result).to.equal(false); }); it('should return false if user is not banned', async () => { Community.findOne.returns(community); const result = await isBannedFromCommunity(user, community._id); - expect(result).to.be.false; + expect(result).to.equal(false); }); it('should return true if user is banned', async () => { community.banned_users.push({ id: user._id }); Community.findOne.returns(community); const result = await isBannedFromCommunity(user, community._id); - expect(result).to.be.true; + expect(result).to.equal(true); }); }); });