diff --git a/tests/stubs/helpers/ZSchemaStub.ts b/tests/stubs/helpers/ZSchemaStub.ts index ce8b3f68..c522a067 100644 --- a/tests/stubs/helpers/ZSchemaStub.ts +++ b/tests/stubs/helpers/ZSchemaStub.ts @@ -12,4 +12,8 @@ export default class ZSchemaStub extends BaseStubClass { @stubMethod() public getLastErrors() { } + + @stubMethod() + public getLastError() { + } } diff --git a/tests/unit/apis/transportAPI.spec.ts b/tests/unit/apis/transportAPI.spec.ts new file mode 100644 index 00000000..6c9afe05 --- /dev/null +++ b/tests/unit/apis/transportAPI.spec.ts @@ -0,0 +1,291 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { SinonSandbox } from 'sinon'; +import { TransportAPI } from '../../../src/apis/transportAPI'; +import { Symbols } from '../../../src/ioc/symbols'; +import { + BlocksModuleStub, + BlocksSubmoduleUtilsStub, + BusStub, + DbStub, + PeersLogicStub, + PeersModuleStub, + TransactionsModuleStub, + TransportModuleStub +} from '../../stubs'; +import { BlockLogicStub } from '../../stubs/logic/BlockLogicStub'; +import { createFakeBlock } from '../../utils/blockCrafter'; +import { createContainer } from '../../utils/containerCreator'; +import { createRandomTransactions } from '../../utils/txCrafter'; + +// tslint:disable-next-line no-var-requires +const assertArrays = require('chai-arrays'); +const expect = chai.expect; +chai.use(chaiAsPromised); +chai.use(assertArrays); + +// tslint:disable no-unused-expression +describe('apis/transportAPI', () => { + let sandbox: SinonSandbox; + let instance: TransportAPI; + let container: Container; + let result: any; + let fakeBlock: any; + let blocksModule: BlocksModuleStub; + let peersLogicStub: PeersLogicStub; + let peersModuleStub: PeersModuleStub; + let transactionsModuleStub: TransactionsModuleStub; + let txs: any; + let transportModuleStub: TransportModuleStub; + let thePeer: any; + let dbStub: DbStub; + let blockLogicStub: BlockLogicStub; + let busStub: BusStub; + let blocksSubmoduleUtilsStub: BlocksSubmoduleUtilsStub; + + beforeEach(() => { + container = createContainer(); + const constants = container.get(Symbols.helpers.constants); + container + .bind(Symbols.modules.peers) + .to(PeersModuleStub) + .inSingletonScope(); + peersModuleStub = container.get(Symbols.modules.peers); + peersModuleStub.enqueueResponse('list', { + consensus: 123, + peers: ['a', 'b', 'c'], + }); + peersModuleStub.enqueueResponse('remove', true); + transactionsModuleStub = container.get(Symbols.modules.transactions); + transactionsModuleStub.enqueueResponse('getMultisignatureTransactionList', [ + { id: '100', signatures: [1, 2, 3] }, + { id: '101', signatures: [] }, + { id: '102', signatures: [1, 2, 3] }, + ]); + transactionsModuleStub.enqueueResponse('getMergedTransactionList', [ + { id: 10 }, + { id: 11 }, + { id: 12 }, + ]); + transportModuleStub = container.get(Symbols.modules.transport); + transportModuleStub.enqueueResponse('receiveTransactions', true); + transportModuleStub.enqueueResponse('receiveTransaction', true); + peersLogicStub = container.get(Symbols.logic.peers); + thePeer = { ip: '8.8.8.8', port: 1234 }; + peersLogicStub.enqueueResponse('create', thePeer); + container.bind(Symbols.api.transport).to(TransportAPI); + dbStub = container.get(Symbols.generic.db); + dbStub.enqueueResponse('query', [true]); + blockLogicStub = container.get(Symbols.logic.block); + blockLogicStub.enqueueResponse('objectNormalize', { id: 123 }); + busStub = container.get(Symbols.helpers.bus); + busStub.enqueueResponse('message', true); + blocksSubmoduleUtilsStub = container.get( + Symbols.modules.blocksSubModules.utils + ); + blocksSubmoduleUtilsStub.enqueueResponse('loadBlocksData', [ + { id: 1 }, + { id: 2 }, + { id: 3 }, + ]); + sandbox = sinon.sandbox.create(); + txs = createRandomTransactions({ send: 10 }); + fakeBlock = createFakeBlock({ + previousBlock: { id: '1', height: 100 } as any, + timestamp: constants.timestamp, + transactions: txs, + }); + blocksModule = container.get(Symbols.modules.blocks); + blocksModule.lastBlock = fakeBlock; + instance = container.get(Symbols.api.transport); + }); + + afterEach(() => { + sandbox.restore(); + sandbox.reset(); + }); + + describe('height()', () => { + it('success', () => { + result = instance.height(); + expect(result).to.deep.equal({ height: 101 }); + }); + }); + + describe('ping()', () => { + it('success', () => { + result = instance.ping(); + expect(result).to.deep.equal({}); + }); + }); + + describe('list()', () => { + it('success', async () => { + result = await instance.list(); + expect(result).to.deep.equal({ peers: ['a', 'b', 'c'] }); + }); + }); + + describe('signatures()', () => { + it('success', () => { + result = instance.signatures(); + expect(result).to.deep.equal({ + signatures: [ + { transaction: '100', signatures: [1, 2, 3] }, + { transaction: '102', signatures: [1, 2, 3] }, + ], + }); + }); + }); + + describe('transactions()', () => { + it('success', () => { + result = instance.transactions(); + expect(result).to.deep.equal({ + transactions: [{ id: 10 }, { id: 11 }, { id: 12 }], + }); + }); + }); + + describe('postTransactions()', () => { + it('Many transactions', async () => { + result = await instance.postTransactions(txs, undefined, { + headers: { port: '1234' }, + ip: '8.8.8.8', + method: 'post', + url: '/foo', + } as any); + expect(peersLogicStub.stubs.create.calledOnce).to.be.true; + expect(peersLogicStub.stubs.create.args[0][0]).to.deep.equal(thePeer); + expect(transportModuleStub.stubs.receiveTransactions.calledOnce).to.be + .true; + expect( + transportModuleStub.stubs.receiveTransactions.args[0][0] + ).to.deep.equal(txs); + expect( + transportModuleStub.stubs.receiveTransactions.args[0][1] + ).to.deep.equal(thePeer); + expect(transportModuleStub.stubs.receiveTransactions.args[0][2]).to.equal( + 'post /foo' + ); + expect(result).to.deep.equal({}); + }); + + it('One transaction', async () => { + await instance.postTransactions( + undefined, + { id: 100 } as any, + { + headers: { port: '1234' }, + ip: '8.8.8.8', + method: 'post', + url: '/foo2', + } as any + ); + expect(peersLogicStub.stubs.create.calledOnce).to.be.true; + expect(peersLogicStub.stubs.create.args[0][0]).to.deep.equal(thePeer); + expect(transportModuleStub.stubs.receiveTransaction.calledOnce).to.be + .true; + expect( + transportModuleStub.stubs.receiveTransaction.args[0][0] + ).to.deep.equal({ id: 100 }); + expect( + transportModuleStub.stubs.receiveTransaction.args[0][1] + ).to.deep.equal(thePeer); + expect(transportModuleStub.stubs.receiveTransaction.args[0][2]).to.be + .false; + expect(transportModuleStub.stubs.receiveTransaction.args[0][3]).to.equal( + 'post /foo2' + ); + }); + }); + + describe('getBlocksCommon()', () => { + it('No ids found', async () => { + await expect( + instance.getBlocksCommon('', { + headers: { port: 1234 }, + ip: '8.8.8.8', + } as any) + ).to.be.rejectedWith('Invalid block id sequence'); + expect(peersModuleStub.stubs.remove.calledOnce).to.be.true; + expect(peersModuleStub.stubs.remove.args[0][0]).to.equal('8.8.8.8'); + expect(peersModuleStub.stubs.remove.args[0][1]).to.equal(1234); + }); + + it('success #1', async () => { + result = await instance.getBlocksCommon('1,2,3', {} as any); + expect(result).to.deep.equal({ common: true }); + expect(dbStub.stubs.query.calledOnce).to.be.true; + // tslint:disable max-line-length + expect(dbStub.stubs.query.args[0][0]).to.equal( + 'SELECT MAX("height") AS "height", "id", "previousBlock", "timestamp" FROM blocks WHERE "id" IN ($1:csv) GROUP BY "id" ORDER BY "height" DESC' + ); + expect(dbStub.stubs.query.args[0][1]).to.deep.equal(['1', '2', '3']); + }); + + it('success #2', async () => { + dbStub.stubs.query.returns([]); + result = await instance.getBlocksCommon('1,2,3', {} as any); + expect(result).to.deep.equal({ common: null }); + expect(dbStub.stubs.query.calledOnce).to.be.true; + // tslint:disable max-line-length + expect(dbStub.stubs.query.args[0][0]).to.equal( + 'SELECT MAX("height") AS "height", "id", "previousBlock", "timestamp" FROM blocks WHERE "id" IN ($1:csv) GROUP BY "id" ORDER BY "height" DESC' + ); + expect(dbStub.stubs.query.args[0][1]).to.deep.equal(['1', '2', '3']); + }); + }); + + describe('postBlock()', () => { + it('success', async () => { + result = await instance.postBlock( + { foo: 'bar' } as any, + { ip: '8.8.8.8', headers: { port: '1234' } } as any + ); + expect(result).to.deep.equal({ blockId: 123 }); + expect(blockLogicStub.stubs.objectNormalize.calledOnce).to.be.true; + expect(blockLogicStub.stubs.objectNormalize.args[0][0]).to.deep.equal({ + foo: 'bar', + }); + expect(busStub.stubs.message.calledOnce).to.be.true; + expect(busStub.stubs.message.args[0][0]).to.equal('receiveBlock'); + expect(busStub.stubs.message.args[0][1]).to.deep.equal({ id: 123 }); + }); + + it('error', async () => { + blockLogicStub.stubs.objectNormalize.throws( + new Error('objectNormalizeError') + ); + await expect( + instance.postBlock( + { foo: 'bar' } as any, + { ip: '8.8.8.8', headers: { port: '1234' } } as any + ) + ).to.be.rejectedWith('objectNormalizeError'); + expect(blockLogicStub.stubs.objectNormalize.calledOnce).to.be.true; + expect(blockLogicStub.stubs.objectNormalize.args[0][0]).to.deep.equal({ + foo: 'bar', + }); + expect(peersModuleStub.stubs.remove.calledOnce).to.be.true; + expect(peersModuleStub.stubs.remove.args[0][0]).to.equal('8.8.8.8'); + expect(peersModuleStub.stubs.remove.args[0][1]).to.equal(1234); + }); + }); + + describe('getBlocks()', () => { + it('success', async () => { + result = await instance.getBlocks('123'); + expect(result).to.deep.equal({ + blocks: [{ id: 1 }, { id: 2 }, { id: 3 }], + }); + expect(blocksSubmoduleUtilsStub.stubs.loadBlocksData.calledOnce).to.be + .true; + expect( + blocksSubmoduleUtilsStub.stubs.loadBlocksData.args[0][0] + ).to.deep.equal({ lastId: '123', limit: 34 }); + }); + }); +}); diff --git a/tests/unit/apis/utils/attachPeerHeaders.spec.ts b/tests/unit/apis/utils/attachPeerHeaders.spec.ts new file mode 100644 index 00000000..e02b7fb1 --- /dev/null +++ b/tests/unit/apis/utils/attachPeerHeaders.spec.ts @@ -0,0 +1,49 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { SinonSandbox } from 'sinon'; +import { AttachPeerHeaders } from '../../../../src/apis/utils/attachPeerHeaders'; +import { Symbols } from '../../../../src/ioc/symbols'; +import { createContainer } from '../../../utils/containerCreator'; + +// tslint:disable-next-line no-var-requires +const assertArrays = require('chai-arrays'); +const expect = chai.expect; +chai.use(chaiAsPromised); +chai.use(assertArrays); + +// tslint:disable no-unused-expression +describe('apis/utils/attachPeerHeaders', () => { + let sandbox: SinonSandbox; + let instance: AttachPeerHeaders; + let request: any; + let response: any; + let responseSpy: any; + let next: any; + let container: Container; + + beforeEach(() => { + container = createContainer(); + container.bind(Symbols.api.utils.attachPeerHeaderToResponseObject).to(AttachPeerHeaders); + sandbox = sinon.sandbox.create(); + response = {set: () => true}; + responseSpy = sandbox.spy(response, 'set'); + request = {}; + next = sandbox.spy(); + instance = container.get(Symbols.api.utils.attachPeerHeaderToResponseObject); + }); + + afterEach(() => { + sandbox.restore(); + sandbox.reset(); + }); + + describe('use()', () => { + it('success', () => { + instance.use(request, response, next); + expect(responseSpy.calledOnce).to.be.true; + expect(next.calledOnce).to.be.true; + }); + }); +}); diff --git a/tests/unit/apis/utils/errorHandler.spec.ts b/tests/unit/apis/utils/errorHandler.spec.ts new file mode 100644 index 00000000..1b549b59 --- /dev/null +++ b/tests/unit/apis/utils/errorHandler.spec.ts @@ -0,0 +1,80 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { SinonSandbox } from 'sinon'; +import { APIErrorHandler } from '../../../../src/apis/utils/errorHandler'; +import { Symbols } from '../../../../src/ioc/symbols'; +import { LoggerStub } from '../../../stubs'; +import { createContainer } from '../../../utils/containerCreator'; + +// tslint:disable-next-line no-var-requires +const assertArrays = require('chai-arrays'); +const expect = chai.expect; +chai.use(chaiAsPromised); +chai.use(assertArrays); + +// tslint:disable no-unused-expression +describe('apis/utils/errorHandler', () => { + let sandbox: SinonSandbox; + let instance: APIErrorHandler; + let request: any; + let response: any; + let responseStatusSpy: any; + let next: any; + let container: Container; + let requestStub: any; + let loggerStub: LoggerStub; + let sendSpy: any; + + beforeEach(() => { + container = createContainer(); + container.bind(Symbols.api.utils.errorHandler).to(APIErrorHandler); + sandbox = sinon.sandbox.create(); + sendSpy = {send: sandbox.spy()}; + response = {status: () => sendSpy }; + responseStatusSpy = sandbox.spy(response, 'status'); + request = {url: {startsWith: () => true}}; + requestStub = sandbox.stub(request.url, 'startsWith'); + next = sandbox.spy(); + loggerStub = container.get(Symbols.helpers.logger); + instance = container.get(Symbols.api.utils.errorHandler); + }); + + afterEach(() => { + sandbox.restore(); + sandbox.reset(); + }); + + describe('error()', () => { + it('If url starts with /peer', () => { + requestStub.returns(true); + instance.error(new Error('Fake error'), request, response, next); + expect(loggerStub.stubs.error.called).to.be.false; + expect(loggerStub.stubs.warn.calledOnce).to.be.true; + expect(loggerStub.stubs.warn.args[0][0]).to.contains('Transport error'); + expect(loggerStub.stubs.warn.args[0][1]).to.equal('Fake error'); + expect(responseStatusSpy.calledOnce).to.be.true; + expect(responseStatusSpy.args[0][0]).to.equal(500); + expect(sendSpy.send.calledOnce).to.be.true; + expect(sendSpy.send.args[0][0]).to.deep.equal({success: false, error: 'Fake error'}); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0]).to.deep.equal({success: false, error: 'Fake error'}); + }); + + it('If url NOT starts with /peer', () => { + requestStub.returns(false); + instance.error('Another fake error', request, response, next); + expect(loggerStub.stubs.warn.called).to.be.false; + expect(loggerStub.stubs.error.calledOnce).to.be.true; + expect(loggerStub.stubs.error.args[0][0]).to.contains('API error'); + expect(loggerStub.stubs.error.args[0][1]).to.equal('Another fake error'); + expect(responseStatusSpy.calledOnce).to.be.true; + expect(responseStatusSpy.args[0][0]).to.equal(500); + expect(sendSpy.send.calledOnce).to.be.true; + expect(sendSpy.send.args[0][0]).to.deep.equal({success: false, error: 'Another fake error'}); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0]).to.deep.equal({success: false, error: 'Another fake error'}); + }); + }); +}); diff --git a/tests/unit/apis/utils/successInterceptor.spec.ts b/tests/unit/apis/utils/successInterceptor.spec.ts new file mode 100644 index 00000000..fa41b141 --- /dev/null +++ b/tests/unit/apis/utils/successInterceptor.spec.ts @@ -0,0 +1,32 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Container } from 'inversify'; +import { SuccessInterceptor } from '../../../../src/apis/utils/successInterceptor'; +import { Symbols } from '../../../../src/ioc/symbols'; +import { createContainer } from '../../../utils/containerCreator'; + +// tslint:disable-next-line no-var-requires +const assertArrays = require('chai-arrays'); +const expect = chai.expect; +chai.use(chaiAsPromised); +chai.use(assertArrays); + +// tslint:disable no-unused-expression +describe('apis/utils/attachPeerHeaders', () => { + let instance: SuccessInterceptor; + let container: Container; + let result: any; + + beforeEach(() => { + container = createContainer(); + container.bind(Symbols.api.utils.successInterceptor).to(SuccessInterceptor); + instance = container.get(Symbols.api.utils.successInterceptor); + }); + + describe('intercept()', () => { + it('success', () => { + result = instance.intercept({foo: 'bar'} as any, {myresult: true}); + expect(result).to.deep.equal({ success: true, myresult: true }); + }); + }); +}); diff --git a/tests/unit/apis/utils/validatePeerHeaders.spec.ts b/tests/unit/apis/utils/validatePeerHeaders.spec.ts new file mode 100644 index 00000000..cf454846 --- /dev/null +++ b/tests/unit/apis/utils/validatePeerHeaders.spec.ts @@ -0,0 +1,138 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Container } from 'inversify'; +import * as sinon from 'sinon'; +import { SinonSandbox } from 'sinon'; +import { ValidatePeerHeaders } from '../../../../src/apis/utils/validatePeerHeaders'; +import { Symbols } from '../../../../src/ioc/symbols'; +import { + PeersLogicStub, + PeersModuleStub, + SystemModuleStub, + ZSchemaStub +} from '../../../stubs'; +import { createContainer } from '../../../utils/containerCreator'; +import { createFakePeer } from '../../../utils/fakePeersFactory'; + +// tslint:disable-next-line no-var-requires +const assertArrays = require('chai-arrays'); +const expect = chai.expect; +chai.use(chaiAsPromised); +chai.use(assertArrays); + +// tslint:disable no-unused-expression +describe('apis/utils/attachPeerHeaders', () => { + let sandbox: SinonSandbox; + let instance: ValidatePeerHeaders; + let container: Container; + let peersLogicStub: PeersLogicStub; + let request: any; + let next: any; + // tslint:disable prefer-const + let appConfig: any; + let systemModuleStub: SystemModuleStub; + let fakePeer: any; + let schemaStub: ZSchemaStub; + let peersModuleStub: PeersModuleStub; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + request = { + headers: { port: 5555, version: '1.0', nethash: 'zxy' }, + ip: '80.1.2.3', + }; + next = sandbox.spy(); + + // Container + container = createContainer(); + container.bind(Symbols.generic.appConfig).toConstantValue(appConfig); + container + .bind(Symbols.api.utils.validatePeerHeadersMiddleware) + .to(ValidatePeerHeaders); + container + .bind(Symbols.modules.peers) + .to(PeersModuleStub) + .inSingletonScope(); + + // Instance + instance = container.get(Symbols.api.utils.validatePeerHeadersMiddleware); + + // systemModuleStub + systemModuleStub = container.get(Symbols.modules.system); + systemModuleStub.enqueueResponse('networkCompatible', true); + systemModuleStub.enqueueResponse('versionCompatible', true); + systemModuleStub.enqueueResponse('getNethash', 'abcd'); + systemModuleStub.enqueueResponse('getMinVersion', '1.0'); + + // peersLogicStub + peersLogicStub = container.get(Symbols.logic.peers); + fakePeer = createFakePeer(); + fakePeer.applyHeaders = sandbox.spy(); + peersLogicStub.enqueueResponse('create', fakePeer); + peersLogicStub.enqueueResponse('upsert', true); + peersLogicStub.enqueueResponse('remove', true); + + // schemaStub + schemaStub = container.get(Symbols.generic.zschema); + schemaStub.enqueueResponse('getLastError', { + details: [{ path: '/foo/bar' }], + }); + schemaStub.enqueueResponse('getLastErrors', [{ message: 'Schema error' }]); + schemaStub.stubs.validate.returns(true); + + // peersModuleStub + peersModuleStub = container.get(Symbols.modules.peers); + peersModuleStub.enqueueResponse('update', true); + }); + + describe('use()', () => { + it('if headers schema is not valid', () => { + schemaStub.stubs.validate.returns(false); + instance.use(request, false, next); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0].message).to.contain('Schema error'); + }); + + it('if is NOT networkCompatible', () => { + systemModuleStub.stubs.networkCompatible.returns(false); + instance.use(request, false, next); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0]).to.deep.equal({ + expected: 'abcd', + message: 'Request is made on the wrong network', + received: 'zxy', + }); + }); + + it('if version is NOT compatible', () => { + systemModuleStub.stubs.versionCompatible.returns(false); + request.headers.version = '3.0'; + instance.use(request, false, next); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0]).to.deep.equal({ + expected: '1.0', + message: 'Request is made from incompatible version', + received: '3.0', + }); + }); + + it('success', () => { + instance.use(request, false, next); + expect(next.calledOnce).to.be.true; + expect(next.args[0][0]).to.equal(undefined); + expect(peersLogicStub.stubs.create.calledOnce).to.be.true; + expect(peersLogicStub.stubs.create.args[0][0]).to.deep.equal({ + ip: '80.1.2.3', + port: 5555, + }); + expect(fakePeer.applyHeaders.calledOnce).to.be.true; + expect(fakePeer.applyHeaders.args[0][0]).to.deep.equal({ + nethash: 'zxy', + port: 5555, + version: '1.0', + }); + expect(peersModuleStub.stubs.update.calledOnce).to.be.true; + expect(peersModuleStub.stubs.update.args[0][0]).to.deep.equal(fakePeer); + }); + }); +});