diff --git a/server/handlers/eventHandler.spec.js b/server/handlers/eventHandler.spec.js new file mode 100644 index 00000000..ad15342e --- /dev/null +++ b/server/handlers/eventHandler.spec.js @@ -0,0 +1,87 @@ +const request = require('supertest'); +const express = require('express'); +const sinon = require('sinon'); +const chai = require('chai'); +const sinonChai = require('sinon-chai'); +const uuid = require('uuid'); +const eventRouter = require('../routes/eventRouter'); +const { errorHandler } = require('../utils/utils'); + +chai.use(sinonChai); +const { expect } = chai; +const ApiKeyService = require('../services/ApiKeyService'); +const JWTService = require('../services/JWTService'); +const EventService = require('../services/EventService'); +const EventEnums = require('../utils/event-enum'); + + +describe ('eventRouter', () => { + let app; + const authenticatedWalletId = uuid.v4(); + + beforeEach(() => { + sinon.stub(ApiKeyService.prototype, 'check'); + sinon.stub(JWTService, 'verify').returns({ + id: authenticatedWalletId, + }); + app = express(); + app.use(express.urlencoded({extended: false})); + app.use(express.json()); + app.use(eventRouter); + app.use(errorHandler); + }) + + afterEach(() => { + sinon.restore(); + }); + + describe('GET /events', () => { + it('missing parameters -- no limit parameter', async() => { + const res = await request(app).get('/events'); + expect(res).property('statusCode').eq(422); + expect(res.body.message).match(/limit.*required/); + }) + + it('wrong parameters -- wrong date format', async() => { + const res = await request(app).get('/events?limit=10&since=test'); + expect(res).property('statusCode').eq(422); + expect(res.body.message).match(/since.*format/); + }) + + it('get all events sucessfully', async() => { + const mockEvents = [ + { + id: 'event1Id', + wallet_id: 'walletId', + type: EventEnums.AUTH.login, + payload: {} + }, + + { + id: 'event2Id', + wallet_id: 'walletId', + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + } + ]; + + const getAllEventsStub = sinon + .stub(EventService.prototype, 'getAllEvents') + .resolves(mockEvents); + + const res = await request(app).get( + '/events?limit=10&since=2021-10-05T14:48:00.000Z&wallet=wallet1'); + + expect(res).property('statusCode').eql(200); + expect(res.body.events).lengthOf(2); + expect(res.body.events).eql(mockEvents); + expect(getAllEventsStub.calledOnceWithExactly({ + wallet: 'wallet1', + limit: '10', + since: '2021-10-05T14:48:00.000Z', + walletLoginId: authenticatedWalletId + })).eql(true); + + }) + }) +}) \ No newline at end of file diff --git a/server/handlers/walletHandler.spec.js b/server/handlers/walletHandler.spec.js index 0a89030f..782d1c01 100644 --- a/server/handlers/walletHandler.spec.js +++ b/server/handlers/walletHandler.spec.js @@ -177,4 +177,129 @@ describe('walletRouter', () => { expect(res.body.message).match(/wallet.*required/); }); }); + + describe('post /wallet/batch-create-wallet', () => { + + const mockCsvContent = `wallet_name,token_transfer_amount_overwrite,extra_wallet_data_about,extra_wallet_data_cover_url,extra_wallet_data_logo_url + wallet1,5,about wallet1,, + wallet2,,about wallet2,wallet2 cover url,wallet2 logo url`; + const mock_file_path = "mockfile.csv"; + const mock_sender_wallet = "wallet"; + const token_transfer_amount_default = 10; + + it('successfully creates wallets', async () => { + + const mockCsvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 5, + extra_wallet_data_about: "about wallet1", + extra_wallet_data_cover_url: '', + extra_wallet_data_logo_url: '' + }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: '', + extra_wallet_data_about: "about wallet2", + extra_wallet_data_cover_url: "wallet2 cover url", + extra_wallet_data_logo_url: "wallet2 logo url" + }, + ]; + + const walletBatchCreateStub = sinon + .stub(WalletService.prototype, 'batchCreateWallet') + .resolves({ + wallets_created: 2, + wallets_already_exists: [], + wallet_other_failure_count: 0, + extra_wallet_information_saved: 0, + extra_wallet_information_not_saved: [], + }); + + const res = await request(app) + .post('/wallets/batch-create-wallet') + .field('sender_wallet', mock_sender_wallet) + .field('token_transfer_amount_default', token_transfer_amount_default) + .attach('csv', Buffer.from(mockCsvContent), mock_file_path); + + expect(res).property('statusCode').eq(201); + expect(walletBatchCreateStub.calledOnce).eql(true); + expect(walletBatchCreateStub.args[0].slice(0,-1)).eql([ + mock_sender_wallet, + token_transfer_amount_default, + authenticatedWalletId, + mockCsvJson + ]); + + + }) + + it('missing parameter', async () => { + + const res = await request(app) + .post('/wallets/batch-create-wallet') + .field('token_transfer_amount_default', token_transfer_amount_default) + .attach('csv', Buffer.from(mockCsvContent), mock_file_path); + + expect(res).property('statusCode').eq(422); + expect(res.body.message).match(/required/); + }) + }) + + describe('post /wallet/batch-transfer', () => { + + const mockCsvContent = `wallet_name,token_transfer_amount_overwrite + wallet1,5 + wallet2, + wallet3,7`; + const mock_file_path = "mockfile.csv"; + const mock_sender_wallet = "wallet"; + const token_transfer_amount_default = 10; + + + it('successfully transfer all tokens', async () => { + + const mockCsvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 5 + }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: '' + }, + { wallet_name: "wallet3", + token_transfer_amount_overwrite: 7 + } + ]; + + const walletBatchTransferWalletStub = sinon + .stub(WalletService.prototype, 'batchTransferWallet') + .resolves(); + + const res = await request(app) + .post('/wallets/batch-transfer') + .field('sender_wallet', mock_sender_wallet) + .field('token_transfer_amount_default', token_transfer_amount_default) + .attach('csv', Buffer.from(mockCsvContent), mock_file_path); + + expect(res).property('statusCode').eq(200); + expect(walletBatchTransferWalletStub.calledOnce).eql(true); + expect(walletBatchTransferWalletStub.args[0].slice(0,-1)).eql([ + mock_sender_wallet, + token_transfer_amount_default, + authenticatedWalletId, + mockCsvJson + ]); + + + }) + + it('missing parameter', async () => { + + const res = await request(app) + .post('/wallets/batch-transfer') + .field('token_transfer_amount_default', token_transfer_amount_default) + .attach('csv', Buffer.from(mockCsvContent), mock_file_path); + + expect(res).property('statusCode').eq(422); + expect(res.body.message).match(/required/); + }) + }) }); diff --git a/server/handlers/walletHandler/index.js b/server/handlers/walletHandler/index.js index 7eac53ac..fcd0319b 100644 --- a/server/handlers/walletHandler/index.js +++ b/server/handlers/walletHandler/index.js @@ -118,7 +118,7 @@ const walletBatchCreate = async (req, res) => { const { sender_wallet, token_transfer_amount_default } = validatedBody; const { wallet_id } = req; const walletService = new WalletService(); - + const result = await walletService.batchCreateWallet( sender_wallet, token_transfer_amount_default, diff --git a/server/handlers/walletHandler/schemas.js b/server/handlers/walletHandler/schemas.js index 16a69846..4f7ab1f5 100644 --- a/server/handlers/walletHandler/schemas.js +++ b/server/handlers/walletHandler/schemas.js @@ -63,7 +63,7 @@ const csvValidationSchema = Joi.array() Joi.string().valid(''), ], extra_wallet_data_about: Joi.string(), - }), + }).options({ allowUnknown: true }), ) .unique('wallet_name') .min(1) diff --git a/server/models/Event.spec.js b/server/models/Event.spec.js new file mode 100644 index 00000000..eef00490 --- /dev/null +++ b/server/models/Event.spec.js @@ -0,0 +1,102 @@ +const sinon = require('sinon'); +const chai = require('chai'); +const sinonChai = require('sinon-chai'); +const uuid = require('uuid'); +const Event = require('./Event'); +const Session = require('../infra/database/Session'); +const EventRepository = require('../repositories/EventRepository'); +const EventEnums = require('../utils/event-enum'); + +chai.use(sinonChai); +const { expect } = chai; + +describe('Event Model', () => { + let eventModel; + let eventRepositoryStub; + + beforeEach(() => { + const session = new Session(); + eventModel = new Event(session); + eventRepositoryStub = sinon.stub(EventRepository.prototype); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('getAllEvents function', () => { + const walletId = uuid.v4(); + const filter = { + and: [], + }; + filter.and.push({ wallet_id: walletId }); + + const mockEvents = [ + { + id: 'event1Id', + wallet_id: 'walletId', + type: EventEnums.AUTH.login, + payload: {} + }, + + { + id: 'event2Id', + wallet_id: 'walletId', + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + } + ]; + + it('should get events', async () => { + + eventRepositoryStub.getAllEvents.resolves(mockEvents); + const result = await eventModel.getAllEvents(walletId, 10); + + expect(result).eql(mockEvents.map(event => ({ ...event}))); + expect(eventRepositoryStub.getAllEvents + .calledOnceWithExactly(filter, 10)).eql(true); + + }); + + it('should get events -- since', async () => { + + const mockSince = '2021-10-05T15:00:00.000Z'; + const filterCopy = JSON.parse(JSON.stringify(filter)); + filterCopy.and.push({ after: { 'wallet_event.created_at': mockSince } }); + + eventRepositoryStub.getAllEvents.resolves(mockEvents); + const result = await eventModel.getAllEvents(walletId, 10, mockSince); + + expect(result).eql(mockEvents.map(event => ({ ...event}))); + expect(eventRepositoryStub.getAllEvents + .calledOnceWithExactly(filterCopy, 10)).eql(true); + + }); + }); + + it('should log event', async () => { + const walletId = uuid.v4(); + const mockEvent = { + id: 'eventId', + wallet_id: 'walletId', + type: EventEnums.AUTH.login, + payload: {} + }; + + eventRepositoryStub.create.resolves(mockEvent) + + const result = await eventModel.logEvent({ + wallet_id: walletId, + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + }); + + expect(result).eql(mockEvent); + expect(eventRepositoryStub.create.calledOnceWithExactly({ + wallet_id: walletId, + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + })).eql(true); + + }) +}); \ No newline at end of file diff --git a/server/repositories/EventRepository.spec.js b/server/repositories/EventRepository.spec.js new file mode 100644 index 00000000..7fcb8ad6 --- /dev/null +++ b/server/repositories/EventRepository.spec.js @@ -0,0 +1,35 @@ +const { expect } = require('chai'); +const mockKnex = require('mock-knex'); +const EventRepository = require('./EventRepository'); +const knex = require('../infra/database/knex'); + +const tracker = mockKnex.getTracker(); +const Session = require('../infra/database/Session'); + + +describe('EventRepository', () => { + let eventRepository; + + beforeEach(() => { + mockKnex.mock(knex); + tracker.install(); + eventRepository = new EventRepository(new Session()); + }); + + afterEach(() => { + tracker.uninstall(); + mockKnex.unmock(knex); + }); + + it('getAllEvents', async () => { + tracker.on('query', (query) => { + expect(query.sql).match( + /select.*wallet_event.*/, + ); + query.response([{}]); + }); + await eventRepository.getByFilter({}); + }); + + +}); \ No newline at end of file diff --git a/server/services/EventService.spec.js b/server/services/EventService.spec.js new file mode 100644 index 00000000..a46dca2f --- /dev/null +++ b/server/services/EventService.spec.js @@ -0,0 +1,101 @@ +const sinon = require('sinon'); +const { expect } = require('chai'); +const EventService = require('./EventService'); +const WalletService = require('./WalletService'); +const Event = require('../models/Event'); +const EventEnums = require('../utils/event-enum'); + +describe('EventService', () => { + let eventService; + + beforeEach(() => { + eventService = new EventService(); + }); + + afterEach(() => { + sinon.restore(); + }) + + describe('getAllEvents', () => { + + let getAllEventsStub; + let hasControlOverByNameStub; + const walletInstance = { + id: 'walletId', + wallet: 'walletInstance' + }; + + beforeEach(() => { + getAllEventsStub = sinon.stub(Event.prototype, 'getAllEvents'); + hasControlOverByNameStub = sinon + .stub(WalletService.prototype, 'hasControlOverByName'); + }); + + it('getAllEvents with wallet', async () => { + + getAllEventsStub.resolves(['event']); + hasControlOverByNameStub.resolves(walletInstance); + + const events = await eventService.getAllEvents({ + wallet: 'wallet', + limit: 10, + since: undefined, + walletLoginId: 'walletLoginId' + }); + + expect(events).eql(['event']); + expect(hasControlOverByNameStub.calledOnceWithExactly + ('walletLoginId', 'wallet')).eql(true); + expect(getAllEventsStub.calledOnceWithExactly + (walletInstance.id, 10, undefined)).eql(true); + + + }); + + it('getAllEvents without wallet', async () => { + + getAllEventsStub.resolves(['event']); + + const events = await eventService.getAllEvents({ + limit: 10, + since: undefined, + walletLoginId: 'walletId' + }); + + expect(events).eql(['event']); + expect(getAllEventsStub.calledOnceWithExactly + ('walletId', 10, undefined)).eql(true); + + }); + + }); + + it('logEvent', async () => { + const event = { + id: 'eventId', + wallet_id: 'walletId', + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + } + + const logEventStub = sinon + .stub(Event.prototype, 'logEvent') + .resolves(event); + + const result = await eventService.logEvent({ + wallet_id: 'walletId', + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + }); + + expect(logEventStub.calledOnceWithExactly({ + wallet_id: 'walletId', + type: EventEnums.TRANSFER.transfer_completed, + payload: {} + })).eql(true); + + expect(result).eql(event); + + }); + +}); \ No newline at end of file diff --git a/server/services/WalletService.js b/server/services/WalletService.js index 32b5a048..fae35b28 100644 --- a/server/services/WalletService.js +++ b/server/services/WalletService.js @@ -208,7 +208,7 @@ class WalletService { for (const wallet of walletsCreated) { const receiverWallet = await this.getByName(wallet.wallet); const walletDetails = csvJson.find( - (w) => w.wallet_name === wallet.wallet, + (w) => w.wallet_name === wallet.name, ); const { @@ -279,7 +279,9 @@ class WalletService { if (status === 'fulfilled') { extraWalletInformationSaved += 1; } else { - extraWalletInformationNotSaved.push(reason); + extraWalletInformationNotSaved.push( + reason.toString().split('Error: ')[1], + ); } } @@ -399,10 +401,10 @@ class WalletService { async hasControlOverByName(parentId, childName) { // - const walletInstance = await this._walletService.getByName(childName); - const isSub = await this._walletService.hasControlOver( + const walletInstance = await this.getByName(childName); + const isSub = await this.hasControlOver( parentId, - childName.id, + walletInstance.id, ); if (!isSub) { throw new HttpError( diff --git a/server/services/WalletService.spec.js b/server/services/WalletService.spec.js index b3f39ca9..08e76f3a 100644 --- a/server/services/WalletService.spec.js +++ b/server/services/WalletService.spec.js @@ -1,11 +1,14 @@ const sinon = require('sinon'); const { expect } = require('chai'); const uuid = require('uuid'); +const fs = require('fs').promises; const WalletService = require('./WalletService'); const Wallet = require('../models/Wallet'); const Session = require('../infra/database/Session'); const Token = require('../models/Token'); const Event = require('../models/Event'); +const Transfer = require('../models/Transfer'); +const HttpError = require('../utils/HttpError'); describe('WalletService', () => { let walletService; @@ -298,4 +301,492 @@ describe('WalletService', () => { }); }); }); + + describe('batchCreateWallet', () => { + + let sessionBeginTransactionStub; + let sessionCommitTransactionStub; + let sessionRollbackTransactionStub; + let sessionIsTransactionInProgressStub; + + let getByNameStub; + let createWalletStub; + let addWalletToMapConfigStub; + let transferBundleStub; + + const loggedInWalletId = uuid.v4(); + const wallet1Id = uuid.v4(); + const wallet2Id = uuid.v4(); + + const mockSenderWallet = { + id: loggedInWalletId, + name: 'wallet' + }; + + const mockFilePath = 'path/to/csv'; + const tokenTransferAmountDefault = 10; + + beforeEach(() => { + + sessionIsTransactionInProgressStub = sinon.stub( + Session.prototype, + 'isTransactionInProgress', + ); + + sessionBeginTransactionStub = sinon + .stub(Session.prototype, 'beginTransaction') + .callsFake(async () => + sessionIsTransactionInProgressStub.returns(true), + ); + + sessionCommitTransactionStub = sinon.stub( + Session.prototype, + 'commitTransaction', + ); + + sessionRollbackTransactionStub = sinon.stub( + Session.prototype, + 'rollbackTransaction', + ); + + getByNameStub = sinon.stub(WalletService.prototype, 'getByName'); + createWalletStub = sinon.stub(WalletService.prototype, 'createWallet'); + addWalletToMapConfigStub = sinon.stub(WalletService.prototype, 'addWalletToMapConfig'); + transferBundleStub = sinon.stub(Transfer.prototype, 'transferBundle'); + + sinon.stub(Token.prototype, 'countTokenByWallet').resolves(20); + sinon.stub(fs, 'unlink').resolves(); + + }); + + it('should successfully create wallets and transfer tokens without errors', async() => { + + const csvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 5, + extra_wallet_data_about: "about wallet1", + extra_wallet_data_cover_url: '', + extra_wallet_data_logo_url: '' }, + + { wallet_name: "wallet2", + token_transfer_amount_overwrite: '', + extra_wallet_data_about: "about wallet2", + extra_wallet_data_cover_url: "wallet2 cover url", + extra_wallet_data_logo_url: "wallet2 logo url" }, + ]; + + const wallet1 = { id: wallet1Id, + name: csvJson[0].wallet_name, + about: csvJson[0].extra_wallet_data_about }; + const wallet2 = { id: wallet2Id, + name: csvJson[1].wallet_name, + about: csvJson[1].extra_wallet_data_about }; + + expect(walletService).instanceOf(WalletService); + getByNameStub.onCall(0).resolves(mockSenderWallet); + getByNameStub.onCall(1).resolves(wallet1); + getByNameStub.onCall(2).resolves(wallet2); + + // Check wallet creation + createWalletStub.onCall(0).resolves(wallet1); + createWalletStub.onCall(1).resolves(wallet2); + const wallets = await walletService.batchCreateWallet(mockSenderWallet.name, + tokenTransferAmountDefault, mockSenderWallet.id, csvJson, mockFilePath); + + expect(createWalletStub.calledTwice).eql(true); + expect(createWalletStub.firstCall.args).eql( + [loggedInWalletId, csvJson[0].wallet_name, csvJson[0].extra_wallet_data_about]); + expect(createWalletStub.secondCall.args).eql( + [loggedInWalletId, csvJson[1].wallet_name, csvJson[1].extra_wallet_data_about]); + + + // check token transfer + expect(transferBundleStub.calledTwice).eql(true); + expect(transferBundleStub.firstCall.args).eql( + [loggedInWalletId, mockSenderWallet, + wallet1, 5, false]); + expect(transferBundleStub.secondCall.args).eql( + [loggedInWalletId, mockSenderWallet, + wallet2, 10, false]); + + + // check extra wallet info + expect(addWalletToMapConfigStub.calledOnceWithExactly({ + walletId: wallet2.id, + walletLogoUrl: csvJson[1].extra_wallet_data_logo_url, + walletCoverUrl: csvJson[1].extra_wallet_data_cover_url, + name: wallet2.name, + })).eql(true); + + expect(wallets).eql({ + wallets_created: 2, + wallets_already_exists: [], + wallet_other_failure_count: 0, + extra_wallet_information_saved: 1, + extra_wallet_information_not_saved: [], + }); + + expect(sessionBeginTransactionStub.calledTwice).eql(true); + expect(sessionCommitTransactionStub.calledTwice).eql(true); + expect(sessionRollbackTransactionStub.notCalled).eql(true); + + }); + + it('should rollback transaction if transfer failed', async() => { + + + const csvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 5, + extra_wallet_data_about: "about wallet1" }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: 5, + extra_wallet_data_about: "about wallet2" }, + ]; + + const wallet1 = { id: wallet1Id, + name: csvJson[0].wallet_name, + about: csvJson[0].extra_wallet_data_about }; + const wallet2 = { id: wallet2Id, + name: csvJson[1].wallet_name, + about: csvJson[1].extra_wallet_data_about }; + + + expect(walletService).instanceOf(WalletService); + getByNameStub.resolves(mockSenderWallet); + + createWalletStub.onCall(0).resolves(wallet1); + createWalletStub.onCall(1).resolves(wallet2); + transferBundleStub.onCall(0).resolves(); + transferBundleStub.onCall(1).rejects(); + + try { + await walletService.batchCreateWallet(mockSenderWallet.name, 10, loggedInWalletId, csvJson, mockFilePath); + } catch (e) {} + + expect(sessionBeginTransactionStub.calledTwice).eql(true); + expect(sessionCommitTransactionStub.calledOnce).eql(true); + expect(sessionRollbackTransactionStub.calledOnce).eql(true); + }); + + it('should catches error count and reason if wallet creation fails', async() => { + + + const csvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 1, + extra_wallet_data_about: "about wallet1" }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: 1, + extra_wallet_data_about: "about wallet2" }, + { wallet_name: "wallet3", + token_transfer_amount_overwrite: '', + extra_wallet_data_about: "about wallet3" } + ]; + + const wallet1 = { id: wallet1Id, + name: csvJson[0].wallet_name, + about: csvJson[0].extra_wallet_data_about }; + const wallet2 = { id: wallet2Id, + name: csvJson[1].wallet_name, + about: csvJson[1].extra_wallet_data_about }; + + + expect(walletService).instanceOf(WalletService); + getByNameStub.resolves(mockSenderWallet); + + createWalletStub.onCall(0).resolves(wallet1); + createWalletStub.onCall(1).rejects( + new HttpError(409, `The wallet "${wallet2.name}" already exists`)); + createWalletStub.onCall(2).rejects(); + + + const wallets = await walletService.batchCreateWallet(mockSenderWallet.name, + 10, loggedInWalletId, csvJson, mockFilePath); + + expect(createWalletStub.firstCall.args).eql( + [loggedInWalletId, csvJson[0].wallet_name, csvJson[0].extra_wallet_data_about]); + expect(createWalletStub.secondCall.args).eql( + [loggedInWalletId, csvJson[1].wallet_name, csvJson[1].extra_wallet_data_about]); + expect(createWalletStub.getCall(2).args).eql( + [loggedInWalletId, csvJson[2].wallet_name, csvJson[2].extra_wallet_data_about]); + + expect(wallets).eql({ + wallets_created: 1, + wallets_already_exists: [`The wallet "${wallet2.name}" already exists`], + wallet_other_failure_count: 1, + extra_wallet_information_saved: 0, + extra_wallet_information_not_saved: [], + }); + + expect(sessionBeginTransactionStub.calledOnce).eql(true); + expect(sessionCommitTransactionStub.calledOnce).eql(true); + expect(sessionRollbackTransactionStub.notCalled).eql(true); + + }) + + + it('should catches error count and reason if extra infomation addition fails', async() => { + + + const csvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 1, + extra_wallet_data_about: "about wallet1", + extra_wallet_data_cover_url: "wallet1 cover url", + extra_wallet_data_logo_url: "wallet1 logo url" }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: 1, + extra_wallet_data_about: "about wallet2", + extra_wallet_data_cover_url: "wallet2 cover url", + extra_wallet_data_logo_url: "wallet2 logo url" }, + ]; + + const wallet1 = { id: wallet1Id, + name: csvJson[0].wallet_name, + about: csvJson[0].extra_wallet_data_about }; + const wallet2 = { id: wallet2Id, + name: csvJson[1].wallet_name, + about: csvJson[1].extra_wallet_data_about }; + + + expect(walletService).instanceOf(WalletService); + getByNameStub.resolves(mockSenderWallet); + + createWalletStub.onCall(0).resolves(wallet1); + createWalletStub.onCall(1).resolves(wallet2); + addWalletToMapConfigStub.onCall(0).resolves(); + addWalletToMapConfigStub.onCall(1).rejects(new Error(`webmap config addition failed`)); + + const wallets = await walletService.batchCreateWallet(mockSenderWallet.name, + 10, loggedInWalletId, csvJson, mockFilePath); + + expect(createWalletStub.firstCall.args).eql( + [loggedInWalletId, csvJson[0].wallet_name, csvJson[0].extra_wallet_data_about]); + expect(createWalletStub.secondCall.args).eql( + [loggedInWalletId, csvJson[1].wallet_name, csvJson[1].extra_wallet_data_about]); + + + + expect(addWalletToMapConfigStub.calledTwice).eql(true); + expect(addWalletToMapConfigStub.firstCall.args[0]).eql({ + walletId: wallet1.id, + walletLogoUrl: csvJson[0].extra_wallet_data_logo_url, + walletCoverUrl: csvJson[0].extra_wallet_data_cover_url, + name: wallet1.name, + }); + + expect(addWalletToMapConfigStub.secondCall.args[0]).eql({ + walletId: wallet2.id, + walletLogoUrl: csvJson[1].extra_wallet_data_logo_url, + walletCoverUrl: csvJson[1].extra_wallet_data_cover_url, + name: wallet2.name, + }); + + expect(wallets).eql({ + wallets_created: 2, + wallets_already_exists: [], + wallet_other_failure_count: 0, + extra_wallet_information_saved: 1, + extra_wallet_information_not_saved: ['webmap config addition failed'], + }); + + expect(sessionBeginTransactionStub.calledTwice).eql(true); + expect(sessionCommitTransactionStub.calledTwice).eql(true); + expect(sessionRollbackTransactionStub.notCalled).eql(true); + + }); + + + }); + + describe('batchTransferWallet', () => { + let sessionBeginTransactionStub; + let sessionCommitTransactionStub; + let sessionRollbackTransactionStub; + let sessionIsTransactionInProgressStub; + + let getByNameStub; + let transferBundleStub; + + const tokenTransferAmountDefault = 10; + const loggedInWalletId = uuid.v4(); + const wallet1Id = uuid.v4(); + const wallet2Id = uuid.v4(); + + const senderWallet = { id: loggedInWalletId, name: 'wallet' }; + const filePath = 'path/to/csv'; + + const csvJson = [ + { wallet_name: "wallet1", + token_transfer_amount_overwrite: 5, + }, + { wallet_name: "wallet2", + token_transfer_amount_overwrite: '', + }, + ]; + + const wallet1 = { + id: wallet1Id, + name: csvJson[0].wallet_name, + about: csvJson[0].extra_wallet_data_about + }; + + const wallet2 = { + id: wallet2Id, + name: csvJson[1].wallet_name, + about: csvJson[1].extra_wallet_data_about + }; + + beforeEach(() => { + + sessionIsTransactionInProgressStub = sinon.stub( + Session.prototype, + 'isTransactionInProgress', + ); + + sessionBeginTransactionStub = sinon + .stub(Session.prototype, 'beginTransaction') + .callsFake(async () => + sessionIsTransactionInProgressStub.returns(true), + ); + + sessionCommitTransactionStub = sinon.stub( + Session.prototype, + 'commitTransaction', + ); + + sessionRollbackTransactionStub = sinon.stub( + Session.prototype, + 'rollbackTransaction', + ); + + getByNameStub = sinon.stub(WalletService.prototype, 'getByName'); + transferBundleStub = sinon.stub(Transfer.prototype, 'transferBundle'); + + sinon.stub(Token.prototype, 'countTokenByWallet').resolves(20); + sinon.stub(fs, 'unlink').resolves(); + + }); + + + it('should rollback if one of transactions fails', async() => { + + expect(walletService).instanceOf(WalletService); + getByNameStub.onCall(0).resolves(senderWallet); + getByNameStub.onCall(1).resolves(wallet1); + getByNameStub.onCall(2).resolves(wallet2); + + transferBundleStub.onCall(0).resolves(); + transferBundleStub.onCall(1).rejects(); + + try { + await walletService.batchTransferWallet( + senderWallet.name, tokenTransferAmountDefault, loggedInWalletId, csvJson, filePath); + } catch (e) {} + + expect(transferBundleStub.calledTwice).eql(true); + expect(transferBundleStub.firstCall.args).eql([ + loggedInWalletId, senderWallet, wallet1, 5, false + ]); + expect(transferBundleStub.secondCall.args).eql([ + loggedInWalletId, senderWallet, wallet2, 10, false + ]); + + expect(sessionBeginTransactionStub.calledOnce).eql(true); + expect(sessionCommitTransactionStub.notCalled).eql(true); + expect(sessionRollbackTransactionStub.calledOnce).eql(true); + + }); + + it('all transactions success', async() => { + + expect(walletService).instanceOf(WalletService); + getByNameStub.onCall(0).resolves(senderWallet); + getByNameStub.onCall(1).resolves(wallet1); + getByNameStub.onCall(2).resolves(wallet2); + + transferBundleStub.onCall(0).resolves(); + transferBundleStub.onCall(1).resolves(); + + const result = await walletService.batchTransferWallet( + senderWallet.name, tokenTransferAmountDefault, loggedInWalletId, csvJson, filePath); + + expect(transferBundleStub.calledTwice).eql(true); + expect(transferBundleStub.firstCall.args).eql([ + loggedInWalletId, senderWallet, wallet1, 5, false + ]); + expect(transferBundleStub.secondCall.args).eql([ + loggedInWalletId, senderWallet, wallet2, 10, false + ]); + + expect(sessionBeginTransactionStub.calledOnce).eql(true); + expect(sessionCommitTransactionStub.calledOnce).eql(true); + expect(sessionRollbackTransactionStub.notCalled).eql(true); + + expect(result.message).eql('Batch transfer successful'); + }); + + + }); + + + describe('hasControlOverByName', () => { + let getByNameStub; + let hasControlOverStub; + + + const parentId = uuid.v4(); + const childId = uuid.v4(); + const childName = 'childWallet'; + + const walletInstance = { + id: childId, + name: childName + }; + + + beforeEach(() => { + + getByNameStub = sinon.stub(WalletService.prototype, 'getByName'); + hasControlOverStub = sinon.stub(WalletService.prototype, 'hasControlOver'); + + }); + + it('should error out -- wallet does not belong to the logged in wallet', async () => { + + expect(walletService).instanceOf(WalletService); + getByNameStub.resolves(walletInstance); + hasControlOverStub.resolves(false); + + try { + await walletService.hasControlOverByName(parentId, childName); + } catch (error) { + expect(error).instanceOf(HttpError); + expect(error.message).eql('Wallet does not belong to the logged in wallet'); + } + + + expect(getByNameStub.calledOnceWithExactly(walletInstance.name)).eql(true); + expect(hasControlOverStub.calledOnceWithExactly(parentId, childId)).eql(true); + + }); + + it('successful', async() => { + + expect(walletService).instanceOf(WalletService); + getByNameStub.resolves(walletInstance); + hasControlOverStub.resolves(true); + + const result = await walletService.hasControlOverByName(parentId, childName); + + expect(getByNameStub.calledOnceWithExactly(walletInstance.name)).eql(true); + expect(hasControlOverStub.calledOnceWithExactly(parentId, childId)).eql(true); + expect(result).eql(walletInstance); + + + }); + + }); + });