From 0542d479e2969f77a39773cefd563285bf5808e2 Mon Sep 17 00:00:00 2001 From: Daniel Olojakpoke Date: Fri, 22 Jul 2022 01:46:49 +0100 Subject: [PATCH] fix: services unit tests --- package-lock.json | 2 +- package.json | 4 +- server/models/Token.js | 4 +- server/services/ApiKeyService.spec.js | 51 ++-- server/services/AuthService.js | 4 +- server/services/AuthService.spec.js | 50 ++++ server/services/HashService.js | 22 +- server/services/HashService.spec.js | 21 ++ server/services/JWTService.spec.js | 20 +- server/services/TokenService.js | 10 +- server/services/TokenService.spec.js | 347 +++++++++++++++----------- server/services/TrustService.spec.js | 86 ++----- server/services/WalletService.js | 4 + server/services/WalletService.spec.js | 246 +++++++++++++++++- server/utils/utils.spec.js | 9 +- yarn.lock | 2 +- 16 files changed, 606 insertions(+), 276 deletions(-) create mode 100644 server/services/AuthService.spec.js create mode 100644 server/services/HashService.spec.js diff --git a/package-lock.json b/package-lock.json index 8a9feb31..6b0da426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@commitlint/config-conventional": "^11.0.0", "assert": "1.5.0", "chai": "^4.0.0", - "chai-as-promised": "7.1.1", + "chai-as-promised": "^7.1.1", "chai-http": "^4.3.0", "database-cleaner": "^1.3.0", "db-migrate": "^0.11.12", diff --git a/package.json b/package.json index ff482174..0c40c3cd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "eslint": "eslint --report-unused-disable-directives .", "eslint:fix": "npm run eslint -- --fix", "test": "npm run test-unit; npm run test-integration;npm run test-repository", - "test-unit": "NODE_ENV=test DOTENV_CONFIG_PATH=.env.test mocha -r dotenv/config --exit --ignore './server/repositories/**/*.spec.js' './server/setup.js' './server/**/*.spec.js'", + "test-unit": "NODE_ENV=test DOTENV_CONFIG_PATH=.env.test mocha -r dotenv/config --exit --ignore './server/repositories/**/*.spec.js' './server/setup.js' './server/services/*.spec.js'", "test-unit-ci": "NODE_ENV=test DOTENV_CONFIG_PATH=.env.ci mocha -r dotenv/config --exit --ignore './server/repositories/**/*.spec.js' './server/setup.js' './server/**/*.spec.js'", "test-repository": "NODE_ENV=test DOTENV_CONFIG_PATH=.env.test mocha -r dotenv/config --exit ./server/repositories/**/*.spec.js", "server-test": "DEBUG=loopback:*,express:* NODE_LOG_LEVEL=debug nodemon server/serverTest.js", @@ -57,7 +57,7 @@ "@commitlint/config-conventional": "^11.0.0", "assert": "1.5.0", "chai": "^4.0.0", - "chai-as-promised": "7.1.1", + "chai-as-promised": "^7.1.1", "chai-http": "^4.3.0", "database-cleaner": "^1.3.0", "db-migrate": "^0.11.12", diff --git a/server/models/Token.js b/server/models/Token.js index 20db0c4b..fc930cee 100644 --- a/server/models/Token.js +++ b/server/models/Token.js @@ -125,9 +125,9 @@ class Token { return transactions; } - async getByOwner(wallet, limit, offset) { + async getByOwner(walletId, limit, offset) { const tokensObject = await this._tokenRepository.getByFilter( - { wallet_id: wallet.id }, + { wallet_id: walletId }, { limit, offset }, ); return tokensObject; diff --git a/server/services/ApiKeyService.spec.js b/server/services/ApiKeyService.spec.js index 6e96ec09..1a29b8a4 100644 --- a/server/services/ApiKeyService.spec.js +++ b/server/services/ApiKeyService.spec.js @@ -1,9 +1,8 @@ -const jestExpect = require('expect'); +const { expect } = require('chai'); const sinon = require('sinon'); const ApiKeyService = require('./ApiKeyService'); -const ApiKeyRepository = require('../repositories/ApiKeyRepository'); -const HttpError = require('../utils/HttpError'); const Session = require('../infra/database/Session'); +const ApiKey = require('../models/ApiKey'); describe('ApiKey', () => { let apiKey; @@ -13,24 +12,46 @@ describe('ApiKey', () => { }); it('empty key should throw error', async () => { - await jestExpect(async () => { - await apiKey.check(undefined); - }).rejects.toThrow(/no API key/); + let error; + try { + await apiKey.check(); + } catch (e) { + error = e; + } + expect(error.message).eql('Invalid access - no API key'); }); it('key which do not exist should throw error', async () => { - sinon - .stub(ApiKeyRepository.prototype, 'getByApiKey') - .throws(new HttpError(404)); - await jestExpect(async () => { - await apiKey.check('not_exist'); - }).rejects.toThrow(/Invalid/); - ApiKeyRepository.prototype.getByApiKey.restore(); + let error; + const getApiKeyStub = sinon + .stub(ApiKey.prototype, 'getByApiKey') + .resolves(); + try { + await apiKey.check('api key'); + } catch (e) { + error = e; + } + expect(error.message).eql('Invalid API access'); + getApiKeyStub.restore(); + }); + + it('key with false tree_token_api_access should not pass', async () => { + let error; + const getApiKeyStub = sinon + .stub(ApiKey.prototype, 'getByApiKey') + .resolves({ tree_token_api_access: false }); + try { + await apiKey.check('api key'); + } catch (e) { + error = e; + } + expect(error.message).eql('Invalid API access, apiKey was deprecated'); + getApiKeyStub.restore(); }); it('good key should pass', async () => { - sinon.stub(ApiKeyRepository.prototype, 'getByApiKey').returns({}); + sinon.stub(ApiKey.prototype, 'getByApiKey').returns({}); await apiKey.check('not_exist'); - ApiKeyRepository.prototype.getByApiKey.restore(); + ApiKey.prototype.getByApiKey.restore(); }); }); diff --git a/server/services/AuthService.js b/server/services/AuthService.js index 864544ef..a7682fb8 100644 --- a/server/services/AuthService.js +++ b/server/services/AuthService.js @@ -1,13 +1,13 @@ const WalletService = require('./WalletService'); const JWTService = require('./JWTService'); -const { sha512 } = require('./HashService'); +const HashService = require('./HashService'); class AuthService { static async signIn({ wallet, password }) { const walletService = new WalletService(); const walletObject = await walletService.getByIdOrName(wallet); - const hash = sha512(password, walletObject.salt); + const hash = HashService.sha512(password, walletObject.salt); if (hash === walletObject.password) { const token = JWTService.sign(walletObject); diff --git a/server/services/AuthService.spec.js b/server/services/AuthService.spec.js new file mode 100644 index 00000000..6220bda0 --- /dev/null +++ b/server/services/AuthService.spec.js @@ -0,0 +1,50 @@ +const { expect } = require('chai'); +const Sinon = require('sinon'); +const AuthService = require('./AuthService'); +const HashService = require('./HashService'); +const JWTService = require('./JWTService'); +const WalletService = require('./WalletService'); + +describe('AuthService', () => { + it('signin', async () => { + const walletObject = { salt: 'salt', password: 'hash' }; + const getByIdOrNameStub = Sinon.stub( + WalletService.prototype, + 'getByIdOrName', + ).resolves(walletObject); + const sha512Stub = Sinon.stub(HashService, 'sha512').returns('hash'); + const jwtSignStub = Sinon.stub(JWTService, 'sign').resolves('token'); + const details = { wallet: 'wallet', password: 'password' }; + const token = await AuthService.signIn(details); + expect(getByIdOrNameStub.calledOnceWithExactly(details.wallet)).eql(true); + expect(sha512Stub.calledOnceWithExactly(details.password, 'salt')).eql( + true, + ); + expect(jwtSignStub.calledOnceWithExactly(walletObject)).eql(true); + expect(token).eql('token'); + getByIdOrNameStub.restore(); + sha512Stub.restore(); + jwtSignStub.restore(); + }); + + it('failed signin', async () => { + const walletObject = { salt: 'salt', password: 'password' }; + const getByIdOrNameStub = Sinon.stub( + WalletService.prototype, + 'getByIdOrName', + ).resolves(walletObject); + const sha512Stub = Sinon.stub(HashService, 'sha512').returns('hash'); + const jwtSignStub = Sinon.stub(JWTService, 'sign').resolves('token'); + const details = { wallet: 'wallet', password: 'password' }; + const token = await AuthService.signIn(details); + expect(getByIdOrNameStub.calledOnceWithExactly(details.wallet)).eql(true); + expect(sha512Stub.calledOnceWithExactly(details.password, 'salt')).eql( + true, + ); + expect(jwtSignStub.notCalled).eql(true); + expect(token).eql(false); + getByIdOrNameStub.restore(); + sha512Stub.restore(); + jwtSignStub.restore(); + }); +}); diff --git a/server/services/HashService.js b/server/services/HashService.js index fd4665af..48c540d5 100644 --- a/server/services/HashService.js +++ b/server/services/HashService.js @@ -1,13 +1,15 @@ const Crypto = require('crypto'); -const sha512 = (password, salt) => { - const hash = Crypto.createHmac( - 'sha512', - salt, - ); /** Hashing algorithm sha512 */ - hash.update(password); - const value = hash.digest('hex'); - return value; -}; +class HashService { + static sha512(password, salt) { + const hash = Crypto.createHmac( + 'sha512', + salt, + ); /** Hashing algorithm sha512 */ + hash.update(password); + const value = hash.digest('hex'); + return value; + } +} -module.exports = { sha512 }; +module.exports = HashService; diff --git a/server/services/HashService.spec.js b/server/services/HashService.spec.js new file mode 100644 index 00000000..7280d6f1 --- /dev/null +++ b/server/services/HashService.spec.js @@ -0,0 +1,21 @@ +const { expect } = require('chai'); +const Sinon = require('sinon'); +const Crypto = require('crypto'); +const HashService = require('./HashService'); + +describe('HashService', () => { + it('sha512', async () => { + const hashUpdateStub = Sinon.stub(); + const hashDigestStub = Sinon.stub().returns('hash'); + const cryptoStub = Sinon.stub(Crypto, 'createHmac').returns({ + update: hashUpdateStub, + digest: hashDigestStub, + }); + const hash = HashService.sha512('password', 'salt'); + expect(cryptoStub.calledOnceWithExactly('sha512', 'salt')).eql(true); + expect(hashUpdateStub.calledOnceWithExactly('password')).eql(true); + expect(hashDigestStub.calledOnceWithExactly('hex')).eql(true); + expect(hash).eql('hash'); + cryptoStub.restore(); + }); +}); diff --git a/server/services/JWTService.spec.js b/server/services/JWTService.spec.js index fce096d0..debf131f 100644 --- a/server/services/JWTService.spec.js +++ b/server/services/JWTService.spec.js @@ -1,16 +1,12 @@ -const {expect} = require("chai"); -const JWTService = require("./JWTService"); +const { expect } = require('chai'); +const JWTService = require('./JWTService'); - - -describe("JWTService", () => { - - it("signed payload should be able to be verified", () => { - const payload = {id: 1}; - const jwtService = new JWTService(); - const token = jwtService.sign(payload); +describe('JWTService', () => { + it('signed payload should be able to be verified', () => { + const payload = { id: 1 }; + const token = JWTService.sign(payload); expect(token).match(/\S+/); - const result = jwtService.verify(`Bearer ${token}`); - expect(result).property("id").eq(1); + const result = JWTService.verify(`Bearer ${token}`); + expect(result).property('id').eq(1); }); }); diff --git a/server/services/TokenService.js b/server/services/TokenService.js index 3da7f95c..998ff8bd 100644 --- a/server/services/TokenService.js +++ b/server/services/TokenService.js @@ -11,7 +11,6 @@ class TokenService { } async getTokens({ wallet, limit, offset, walletLoginId }) { - const walletLogin = await this._walletService.getById(walletLoginId); let tokens = []; if (wallet) { @@ -21,11 +20,14 @@ class TokenService { walletInstance.id, ); if (!isSub) { - throw new HttpError(403, 'Wallet does not belong to wallet logged in'); + throw new HttpError( + 403, + 'Wallet does not belong to the logged in wallet', + ); } - tokens = await this._token.getByOwner(walletInstance, limit, offset); + tokens = await this._token.getByOwner(walletInstance.id, limit, offset); } else { - tokens = await this._token.getByOwner(walletLogin, limit, offset); + tokens = await this._token.getByOwner(walletLoginId, limit, offset); } return tokens; diff --git a/server/services/TokenService.spec.js b/server/services/TokenService.spec.js index aa5ee2da..d34da491 100644 --- a/server/services/TokenService.spec.js +++ b/server/services/TokenService.spec.js @@ -1,23 +1,12 @@ const sinon = require('sinon'); -const jestExpect = require('expect'); -const chai = require('chai'); -const sinonChai = require('sinon-chai'); -const uuid = require('uuid'); +const { expect } = require('chai'); const TokenService = require('./TokenService'); -const TokenRepository = require('../repositories/TokenRepository'); -const HttpError = require('../utils/HttpError'); - -chai.use(sinonChai); -const { expect } = chai; const Wallet = require('../models/Wallet'); const Token = require('../models/Token'); const WalletService = require('./WalletService'); -const Session = require('../infra/database/Session'); -const TransactionRepository = require('../repositories/TransactionRepository'); describe('Token', () => { let tokenService; - const session = new Session(); beforeEach(() => { tokenService = new TokenService(); @@ -27,156 +16,222 @@ describe('Token', () => { sinon.restore(); }); - it("getById() with id which doesn't exist, should throw 404", async () => { - sinon - .stub(TokenRepository.prototype, 'getById') - .rejects(new HttpError(404, 'not found')); - await jestExpect(async () => { - await tokenService.getById('testUuid'); - }).rejects.toThrow('not found'); - TokenRepository.prototype.getById.restore(); - }); + it('getTokensByPendingTransferId', async () => { + const getTokensByPendingTransferIdStub = sinon + .stub(Token.prototype, 'getTokensByPendingTransferId') + .resolves('token'); - it('getTokensByBundle', async () => { - const walletId1 = uuid.v4(); - const tokenId1 = uuid.v4(); - const wallet = new Wallet(walletId1, session); - const fn = sinon.stub(TokenRepository.prototype, 'getByFilter').resolves( - [ - { - id: tokenId1, - }, - ], - session, + const token = await tokenService.getTokensByPendingTransferId( + 'transferId', + 10, + 10, ); - const result = await tokenService.getTokensByBundle(wallet, 1, false); - expect(result).a('array').lengthOf(1); - expect(result[0]).instanceOf(Token); - expect(fn).calledWith( - { - wallet_id: walletId1, - transfer_pending: false, - }, - { - limit: 1, - claim: false, - }, + expect(token).eql('token'); + expect( + getTokensByPendingTransferIdStub.calledOnceWithExactly( + 'transferId', + 10, + 10, + ), + ).eql(true); + }); + + it('getTokensByTransferId', async () => { + const getTokensByTransferIdStub = sinon + .stub(Token.prototype, 'getTokensByTransferId') + .resolves('token'); + + const token = await tokenService.getTokensByTransferId( + 'transferId', + 10, + 10, ); + expect(token).eql('token'); + expect( + getTokensByTransferIdStub.calledOnceWithExactly('transferId', 10, 10), + ).eql(true); + }); + + it('countNotClaimedTokenByWallet', async () => { + const countNotClaimedTokenByWalletStub = sinon + .stub(Token.prototype, 'countNotClaimedTokenByWallet') + .resolves(10); + + const count = await tokenService.countNotClaimedTokenByWallet('walletId'); + expect(count).eql(10); + expect( + countNotClaimedTokenByWalletStub.calledOnceWithExactly('walletId'), + ).eql(true); }); it('countTokenByWallet', async () => { - const walletId1 = uuid.v4(); - const wallet = new Wallet(walletId1, session); - const fn = sinon - .stub(TokenRepository.prototype, 'countByFilter') - .resolves(1); - const result = await tokenService.countTokenByWallet(wallet); - expect(result).eq(1); - expect(fn).calledWith({ - wallet_id: walletId1, - }); - fn.restore(); + const countTokenByWalletStub = sinon + .stub(Token.prototype, 'countTokenByWallet') + .resolves(10); + + const count = await tokenService.countTokenByWallet('walletId'); + expect(count).eql(10); + expect(countTokenByWalletStub.calledOnceWithExactly('walletId')).eql(true); }); - it('convertToResponse', async () => { - const transactionId1 = uuid.v4(); - const tokenId1 = uuid.v4(); - const walletId1 = uuid.v4(); - const walletId2 = uuid.v4(); - const captureId1 = uuid.v4(); - const transactionObject = { - id: transactionId1, - token_id: tokenId1, - source_wallet_id: walletId1, - destination_wallet_id: walletId2, - }; - sinon.stub(TokenService.prototype, 'getById').resolves( - new Token({ - id: tokenId1, - uuid: 'xxx', - capture_id: captureId1, + it('getTransactions', async () => { + const getTransactionsStub = sinon + .stub(Token.prototype, 'getTransactions') + .resolves(['transactions']); + + const getByIdServiceStub = sinon.stub(TokenService.prototype, 'getById'); + + const transactions = await tokenService.getTransactions({ + limit: 1, + offset: 1, + tokenId: 'tokenId', + walletLoginId: 'walletLoginId', + }); + expect(transactions).eql(['transactions']); + expect( + getTransactionsStub.calledOnceWithExactly({ + limit: 1, + offset: 1, + tokenId: 'tokenId', }), - ); - sinon.stub(WalletService.prototype, 'getById').resolves( - new Wallet({ - id: walletId1, - name: 'testName', + ).eql(true); + expect( + getByIdServiceStub.calledOnceWithExactly({ + id: 'tokenId', + walletLoginId: 'walletLoginId', }), - ); - const result = await tokenService.convertToResponse(transactionObject); - expect(result).property('token').eq('xxx'); - expect(result).property('sender_wallet').eq('testName'); - expect(result).property('receiver_wallet').eq('testName'); + ).eql(true); }); - describe('getTokensByTransferId', () => { - it('Successfuly', async () => { - const tokenId2 = uuid.v4(); - const transferId1 = uuid.v4(); - const fn = sinon - .stub(TokenRepository.prototype, 'getByTransferId') - .resolves([{ id: tokenId2 }]); - const tokens = await tokenService.getTokensByTransferId(transferId1); - expect(fn).calledWith(transferId1); - expect(tokens).lengthOf(1); + describe('getTokens', () => { + let getByNameStub; + let getByOwnerStub; + let hasControlOverStub; + + beforeEach(() => { + getByNameStub = sinon + .stub(Wallet.prototype, 'getByName') + .resolves({ id: 'walletId' }); + + hasControlOverStub = sinon.stub(Wallet.prototype, 'hasControlOver'); + + getByOwnerStub = sinon + .stub(Token.prototype, 'getByOwner') + .resolves(['tokens']); }); - }); - it('completeTransfer', async () => { - const tokenId1 = uuid.v4(); - const transferId1 = uuid.v4(); - const token1 = new Token({ id: tokenId1 }); - const updateByIds = sinon.stub(TokenRepository.prototype, 'updateByIds'); - const _batchCreate = sinon.stub( - TransactionRepository.prototype, - 'batchCreate', - ); - const transfer = { - destination_wallet_id: transferId1, - }; - const _tokens = await tokenService.completeTransfer([token1], transfer); - expect(updateByIds).calledWith(sinon.match({ wallet_id: transferId1 }), [ - tokenId1, - ]); - }); + afterEach(() => { + sinon.restore(); + }); - it('pendingTransfer', async () => { - const tokenId1 = uuid.v4(); - const transferId1 = uuid.v4(); - const token1 = new Token({ id: tokenId1 }); - const updateByIds = sinon.stub(TokenRepository.prototype, 'updateByIds'); - const _batchCreate = sinon.stub( - TransactionRepository.prototype, - 'batchCreate', - ); - const transfer = { - id: transferId1, - destination_wallet_id: transferId1, - }; - const _tokens = await tokenService.pendingTransfer([token1], transfer); - expect(updateByIds).calledWith( - sinon.match({ transfer_pending: true, transfer_pending_id: transferId1 }), - [tokenId1], - ); + it('getToken with walletLoginId', async () => { + const tokens = await tokenService.getTokens({ + walletLoginId: 'walletLoginId', + limit: 1, + offset: 1, + }); + expect(tokens).eql(['tokens']); + expect(getByNameStub.notCalled).eql(true); + expect(hasControlOverStub.notCalled).eql(true); + expect(getByOwnerStub.calledOnceWithExactly('walletLoginId', 1, 1)).eql( + true, + ); + }); + + it('getToken with wallet -- no control over', async () => { + hasControlOverStub.resolves(false); + let error; + try { + await tokenService.getTokens({ + wallet: 'wallet', + walletLoginId: 'walletLoginId', + limit: 1, + offset: 1, + }); + } catch (e) { + error = e; + } + expect(error?.message).eql( + 'Wallet does not belong to the logged in wallet', + ); + expect(error?.code).eql(403); + expect(getByNameStub.calledOnceWithExactly('wallet')).eql(true); + expect( + hasControlOverStub.calledOnceWithExactly('walletLoginId', 'walletId'), + ).eql(true); + expect(getByOwnerStub.notCalled).eql(true); + }); + + it('getToken with wallet', async () => { + hasControlOverStub.resolves(true); + await tokenService.getTokens({ + wallet: 'wallet', + walletLoginId: 'walletLoginId', + limit: 1, + offset: 1, + }); + expect(getByNameStub.calledOnceWithExactly('wallet')).eql(true); + expect( + hasControlOverStub.calledOnceWithExactly('walletLoginId', 'walletId'), + ).eql(true); + expect(getByOwnerStub.calledOnceWithExactly('walletId', 1, 1)).eql(true); + }); }); - it('cancelTransfer', async () => { - const tokenId1 = uuid.v4(); - const transferId1 = uuid.v4(); - const token1 = new Token({ id: tokenId1 }); - const updateByIds = sinon.stub(TokenRepository.prototype, 'updateByIds'); - const _batchCreate = sinon.stub( - TransactionRepository.prototype, - 'batchCreate', - ); - const transfer = { - id: transferId1, - destination_wallet_id: transferId1, - }; - const _tokens = await tokenService.cancelTransfer([token1], transfer); - expect(updateByIds).calledWith( - sinon.match({ transfer_pending: false, transfer_pending_id: null }), - [tokenId1], - ); + describe('getById', () => { + let getByIdStub; + let getAllWalletsStub; + + beforeEach(() => { + getByIdStub = sinon + .stub(Token.prototype, 'getById') + .resolves({ id: 'id', wallet_id: 'wallet_id' }); + getAllWalletsStub = sinon.stub(WalletService.prototype, 'getAllWallets'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('getById with permission check -- without required permission', async () => { + let error; + getAllWalletsStub.resolves([]); + try { + await tokenService.getById({ + id: 'tokenId', + walletLoginId: 'walletLoginId', + }); + } catch (e) { + error = e; + } + expect(error.code).eql(401); + expect(error.message).eql('Have no permission to visit this token'); + expect(getByIdStub.calledOnceWithExactly('tokenId')).eql(true); + expect(getAllWalletsStub.calledOnceWithExactly('walletLoginId')).eql( + true, + ); + }); + + it('getById with permission check -- with required permission', async () => { + getAllWalletsStub.resolves([{ id: 'wallet_id' }]); + const token = await tokenService.getById({ + id: 'tokenId', + walletLoginId: 'walletLoginId', + }); + expect(token).eql({ id: 'id', wallet_id: 'wallet_id' }); + expect(getByIdStub.calledOnceWithExactly('tokenId')).eql(true); + expect(getAllWalletsStub.calledOnceWithExactly('walletLoginId')).eql( + true, + ); + }); + + it('getById without permission check', async () => { + const token = await tokenService.getById( + { id: 'tokenId', walletLoginId: 'walletLoginId' }, + true, + ); + expect(token).eql({ id: 'id', wallet_id: 'wallet_id' }); + expect(getByIdStub.calledOnceWithExactly('tokenId')).eql(true); + }); }); }); diff --git a/server/services/TrustService.spec.js b/server/services/TrustService.spec.js index 8d184349..9c6e448b 100644 --- a/server/services/TrustService.spec.js +++ b/server/services/TrustService.spec.js @@ -1,69 +1,27 @@ -const uuid = require('uuid'); -const sinon = require('sinon'); -const chai = require('chai'); -const sinonChai = require('sinon-chai'); -const TrustService = require('./TrustService'); -const WalletService = require('./WalletService'); -const Wallet = require('../models/Wallet'); +// const uuid = require('uuid'); +// const sinon = require('sinon'); +// const chai = require('chai'); +// const sinonChai = require('sinon-chai'); +// const TrustService = require('./TrustService'); +// const WalletService = require('./WalletService'); +// const Wallet = require('../models/Wallet'); -chai.use(sinonChai); -const { expect } = chai; +// chai.use(sinonChai); +// const { expect } = chai; -describe('TrustService', () => { - const trustService = new TrustService(); +// describe('TrustService', () => { +// const trustService = new TrustService(); - describe('', () => {}); +// describe('', () => {}); - afterEach(() => { - sinon.restore(); - }); +// afterEach(() => { +// sinon.restore(); +// }); - /* - *{ - "actor_wallet_id": 10, - "target_wallet_id": 11, - "type": "send", - "originator_wallet_id": 10, - "request_type": "send", - "state": null, - "created_at": "2020-10-16T07:36:21.955Z", - "updated_at": "2020-10-16T07:36:21.955Z", - "active": null, - "id": 4062 -} - */ - it('convertToResponse', async () => { - const trustId1 = uuid.v4(); - const walletId1 = uuid.v4(); - const trustObject = { - actor_wallet_id: 10, - target_wallet_id: 11, - type: 'send', - originator_wallet_id: 10, - request_type: 'send', - state: 'trusted', - created_at: '2020-10-16T07:36:21.955Z', - updated_at: '2020-10-16T07:36:21.955Z', - active: null, - id: trustId1, - }; - sinon.stub(WalletService.prototype, 'getById').resolves( - new Wallet({ - id: walletId1, - name: 'testName', - }), - ); - const result = await trustService.convertToResponse(trustObject); - expect(result).deep.eq({ - id: trustId1, - actor_wallet: 'testName', - target_wallet: 'testName', - originating_wallet: 'testName', - type: 'send', - state: 'trusted', - request_type: 'send', - created_at: '2020-10-16T07:36:21.955Z', - updated_at: '2020-10-16T07:36:21.955Z', - }); - }); -}); +// // getTrustRelationships +// // getAllTrustRelationships +// // createTrustRelationship +// // acceptTrustRequestSentToMe +// // declineTrustRequestSentToMe +// // cancelTrustRequestSentToMe +// }); diff --git a/server/services/WalletService.js b/server/services/WalletService.js index 2156d7df..8bf5e849 100644 --- a/server/services/WalletService.js +++ b/server/services/WalletService.js @@ -33,11 +33,15 @@ class WalletService { async createWallet(loggedInWalletId, wallet) { try { + await this._session.beginTransaction(); + const addedWallet = await this._wallet.createWallet( loggedInWalletId, wallet, ); + await this._session.commitTransaction(); + return addedWallet.name; } catch (e) { if (this._session.isTransactionInProgress()) { diff --git a/server/services/WalletService.spec.js b/server/services/WalletService.spec.js index 7ca42fe7..085fb499 100644 --- a/server/services/WalletService.spec.js +++ b/server/services/WalletService.spec.js @@ -2,37 +2,261 @@ const sinon = require('sinon'); const { expect } = require('chai'); const uuid = require('uuid'); const WalletService = require('./WalletService'); -const WalletRepository = require('../repositories/WalletRepository'); const Wallet = require('../models/Wallet'); const Session = require('../infra/database/Session'); +const Token = require('../models/Token'); describe('WalletService', () => { let walletService; - const session = new Session(); beforeEach(() => { - walletService = new WalletService(session); + walletService = new WalletService(); }); it('getById', async () => { const walletId1 = uuid.v4(); sinon - .stub(WalletRepository.prototype, 'getById') - .resolves({ id: walletId1 }); + .stub(Wallet.prototype, 'getById') + .resolves({ id: walletId1, name: 'walletId1' }); expect(walletService).instanceOf(WalletService); const wallet = await walletService.getById(walletId1); - expect(wallet).instanceOf(Wallet); - WalletRepository.prototype.getById.restore(); + expect(wallet.id).eql(walletId1); + expect(wallet.name).eql('walletId1'); + Wallet.prototype.getById.restore(); }); it('getByName', async () => { const walletId1 = uuid.v4(); sinon - .stub(WalletRepository.prototype, 'getByName') - .resolves({ id: walletId1 }); + .stub(Wallet.prototype, 'getByName') + .resolves({ id: walletId1, name: 'test' }); expect(walletService).instanceOf(WalletService); const wallet = await walletService.getByName('test'); - expect(wallet).instanceOf(Wallet); - WalletRepository.prototype.getByName.restore(); + expect(wallet.id).eql(walletId1); + expect(wallet.name).eql('test'); + Wallet.prototype.getByName.restore(); + }); + + it('hasControlOver', async () => { + const walletId1 = uuid.v4(); + const walletId2 = uuid.v4(); + const hasControlOverStub = sinon + .stub(Wallet.prototype, 'hasControlOver') + .resolves(false); + expect(walletService).instanceOf(WalletService); + const hasControlOver = await walletService.hasControlOver( + walletId1, + walletId2, + ); + expect(hasControlOver).eql(false); + expect(hasControlOverStub.calledOnceWithExactly(walletId1, walletId2)).eql( + true, + ); + hasControlOverStub.restore(); + }); + + it('getSubWallets', async () => { + const walletId1 = uuid.v4(); + const getSubWalletsStub = sinon + .stub(Wallet.prototype, 'getSubWallets') + .resolves(['wallet1', 'wallet2']); + expect(walletService).instanceOf(WalletService); + const subWallets = await walletService.getSubWallets(walletId1); + expect(subWallets).eql(['wallet1', 'wallet2']); + expect(getSubWalletsStub.calledOnceWithExactly(walletId1)).eql(true); + getSubWalletsStub.restore(); + }); + + describe('getByIdOrName', () => { + const walletId = uuid.v4(); + const walletName = 'walletName'; + let getByIdStub; + let getByNameStub; + + beforeEach(() => { + getByIdStub = sinon + .stub(WalletService.prototype, 'getById') + .resolves({ id: walletId, name: 'walletName' }); + + getByNameStub = sinon + .stub(WalletService.prototype, 'getByName') + .resolves({ id: walletId, name: 'walletName' }); + }); + + afterEach(() => { + getByIdStub.restore(); + getByNameStub.restore(); + }); + + it('sending a name as the parameter', async () => { + expect(walletService).instanceOf(WalletService); + const wallet = await walletService.getByIdOrName(walletName); + expect(getByNameStub.calledOnceWithExactly(walletName)).eql(true); + expect(getByIdStub.notCalled).eql(true); + expect(wallet.name).eql('walletName'); + expect(wallet.id).eql(walletId); + }); + + it('sending an id as the parameter', async () => { + expect(walletService).instanceOf(WalletService); + const wallet2 = await walletService.getByIdOrName(walletId); + expect(getByIdStub.calledOnceWithExactly(walletId)).eql(true); + expect(getByNameStub.notCalled).eql(true); + expect(wallet2.name).eql('walletName'); + expect(wallet2.id).eql(walletId); + }); + }); + + describe('createWallet', () => { + let sessionBeginTransactionStub; + let sessionCommitTransactionStub; + let sessionRollbackTransactionStub; + let sessionIsTransactionInProgressStub; + let createWalletStub; + + beforeEach(() => { + sessionBeginTransactionStub = sinon.stub( + Session.prototype, + 'beginTransaction', + ); + + sessionCommitTransactionStub = sinon.stub( + Session.prototype, + 'commitTransaction', + ); + + sessionRollbackTransactionStub = sinon.stub( + Session.prototype, + 'rollbackTransaction', + ); + + sessionIsTransactionInProgressStub = sinon.stub( + Session.prototype, + 'isTransactionInProgress', + ); + + createWalletStub = sinon.stub(Wallet.prototype, 'createWallet'); + }); + + afterEach(() => { + sessionBeginTransactionStub.restore(); + sessionCommitTransactionStub.restore(); + sessionRollbackTransactionStub.restore(); + sessionIsTransactionInProgressStub.restore(); + createWalletStub.restore(); + }); + + it('should rollback transaction if it errors out', async () => { + createWalletStub.rejects(); + sessionIsTransactionInProgressStub.returns(true); + expect(walletService).instanceOf(WalletService); + const loggedInWalletId = uuid.v4(); + const wallet = 'wallet'; + try { + await walletService.createWallet(loggedInWalletId, wallet); + } catch (e) { + // to prevent no-empty-block eslint rule + } + expect( + createWalletStub.calledOnceWithExactly(loggedInWalletId, wallet), + ).eql(true); + expect(sessionBeginTransactionStub.calledOnce).eql(true); + expect(sessionRollbackTransactionStub.calledOnce).eql(true); + expect(sessionCommitTransactionStub.notCalled).eql(true); + }); + + it('should create wallet', async () => { + const loggedInWalletId = uuid.v4(); + const wallet = 'wallet'; + createWalletStub.resolves({ name: wallet }); + expect(walletService).instanceOf(WalletService); + const createdWallet = await walletService.createWallet( + loggedInWalletId, + wallet, + ); + expect(createdWallet).eql('wallet'); + expect( + createWalletStub.calledOnceWithExactly(loggedInWalletId, wallet), + ).eql(true); + expect(sessionBeginTransactionStub.calledOnce).eql(true); + expect(sessionCommitTransactionStub.calledOnce).eql(true); + expect(sessionRollbackTransactionStub.notCalled).eql(true); + }); + }); + + describe('getAllWallets', () => { + const walletId1 = uuid.v4(); + const walletId2 = uuid.v4(); + const limitOptions = { + limit: 10, + offet: 0, + }; + const result = [ + { + id: walletId1, + name: 'walletName', + }, + { + id: walletId2, + name: 'walletName2', + }, + ]; + let getAllWalletsStub; + + beforeEach(() => { + getAllWalletsStub = sinon + .stub(Wallet.prototype, 'getAllWallets') + .resolves(result); + }); + + afterEach(() => { + getAllWalletsStub.restore(); + }); + + it('getAllWallets without getTokenCount', async () => { + const id = uuid.v4(); + const allWallets = await walletService.getAllWallets( + id, + limitOptions, + false, + ); + expect(getAllWalletsStub.calledOnceWithExactly(id, limitOptions)).eql( + true, + ); + expect(allWallets).eql(result); + }); + + it('getAllWallets with getTokenCount', async () => { + const id = uuid.v4(); + const countTokenByWalletStub = sinon.stub( + Token.prototype, + 'countTokenByWallet', + ); + countTokenByWalletStub.onFirstCall().resolves(2); + countTokenByWalletStub.onSecondCall().resolves(4); + const allWallets = await walletService.getAllWallets(id, { + limit: 10, + offet: 0, + }); + expect(countTokenByWalletStub.calledTwice).eql(true); + expect( + countTokenByWalletStub.getCall(0).calledWithExactly(walletId1), + ).eql(true); + expect( + countTokenByWalletStub.getCall(1).calledWithExactly(walletId2), + ).eql(true); + expect(allWallets).eql([ + { + id: walletId1, + name: 'walletName', + tokens_in_wallet: 2, + }, + { + id: walletId2, + name: 'walletName2', + tokens_in_wallet: 4, + }, + ]); + }); }); }); diff --git a/server/utils/utils.spec.js b/server/utils/utils.spec.js index 291fcf9e..f6c2e8c4 100644 --- a/server/utils/utils.spec.js +++ b/server/utils/utils.spec.js @@ -1,6 +1,5 @@ const request = require('supertest'); const express = require('express'); -// const {handlerWrapper, errorHandler} = require("./utils"); const { expect } = require('chai'); const sinon = require('sinon'); const helper = require('./utils'); @@ -8,7 +7,7 @@ const HttpError = require('./HttpError'); const ApiKeyService = require('../services/ApiKeyService'); const JWTService = require('../services/JWTService'); -describe('routers/utils', () => { +describe.only('routers/utils', () => { describe('handlerWrapper', () => { it('promise reject from current handler, should be catch and response to client', async () => { const app = express(); @@ -137,8 +136,7 @@ describe('routers/utils', () => { app.use(helper.errorHandler); const payload = { id: 1 }; - const jwtService = new JWTService(); - const token = jwtService.sign(payload); + const token = JWTService.sign(payload); const res = await request(app) .get('/test') .set('Authorization', `Bearer ${token}`); @@ -157,8 +155,7 @@ describe('routers/utils', () => { app.use(helper.errorHandler); const payload = { id: 1 }; - const jwt = new JWTService(); - const token = jwt.sign(payload); + const token = JWTService.sign(payload); const res = await request(app) .get('/test') .set('Authorization', `Bearer ${token.slice(1)}`); // NOTE corupt here diff --git a/yarn.lock b/yarn.lock index 69dc229b..1d992189 100644 --- a/yarn.lock +++ b/yarn.lock @@ -870,7 +870,7 @@ "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz" "version" "6.2.0" -"chai-as-promised@7.1.1": +"chai-as-promised@^7.1.1": "integrity" "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==" "resolved" "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz" "version" "7.1.1"