diff --git a/packages/stack/contracts-manager/package.json b/packages/stack/contracts-manager/package.json index 0a45652b1..3f235f9e4 100644 --- a/packages/stack/contracts-manager/package.json +++ b/packages/stack/contracts-manager/package.json @@ -41,7 +41,8 @@ "lint:ts": "tslint -c tslint.json \"src/**/*.ts\"", "qa": "npm-run-all lint _typecheck _build", "reset": "npx rimraf dist embark-*.tgz package", - "solo": "embark-solo" + "solo": "embark-solo", + "test": "jest" }, "eslintConfig": { "extends": "../../../.eslintrc.json" @@ -58,8 +59,11 @@ "web3-utils": "1.2.6" }, "devDependencies": { + "babel-jest": "24.9.0", "embark-solo": "^5.2.3", + "embark-testing": "^5.2.0-nightly.3", "eslint": "6.8.0", + "jest": "24.9.0", "npm-run-all": "4.1.5", "rimraf": "3.0.0", "tslint": "5.20.1", @@ -69,5 +73,20 @@ "node": ">=10.17.0", "npm": ">=6.11.3", "yarn": ">=1.19.1" + }, + "jest": { + "collectCoverage": true, + "testEnvironment": "node", + "testMatch": [ + "**/test/**/*.js" + ], + "transform": { + "\\.(js|ts)$": [ + "babel-jest", + { + "rootMode": "upward" + } + ] + } } } diff --git a/packages/stack/contracts-manager/src/index.js b/packages/stack/contracts-manager/src/index.js index 8ca81c761..c69f60004 100644 --- a/packages/stack/contracts-manager/src/index.js +++ b/packages/stack/contracts-manager/src/index.js @@ -62,6 +62,10 @@ export default class ContractsManager { })(); } + set web3(val) { + this._web3 = val; + } + registerCommands() { const self = this; diff --git a/packages/stack/contracts-manager/test/contracts-manager.spec.js b/packages/stack/contracts-manager/test/contracts-manager.spec.js new file mode 100644 index 000000000..7d37dafd7 --- /dev/null +++ b/packages/stack/contracts-manager/test/contracts-manager.spec.js @@ -0,0 +1,239 @@ +import ContractsManager from '../src'; +import Contract from '../src/contract'; + +import sinon from 'sinon'; +import assert from 'assert'; +import { fakeEmbark } from 'embark-testing'; + +const { embark, plugins } = fakeEmbark(); + +// Due to our `DAPP_PATH` dependency in `embark-utils` `dappPath()`, we need to +// ensure that this environment variable is defined. +process.env.DAPP_PATH = 'something'; + +describe('stack/contracts-manager', () => { + + let contractsManager; + + beforeEach(() => { + contractsManager = new ContractsManager(embark, { plugins }); + }); + + afterEach(() => { + embark.teardown(); + sinon.restore(); + }); + + describe('instantiation', () => { + + it('should register contracts:reset command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:reset'); + }); + + + it('should register contracts:build command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:build'); + }); + + it('should register contracts:list command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:list'); + }); + + it('should register contracts:contract command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:contract'); + }); + + it('should register contracts:add command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:add'); + }); + + it('should register contracts:all command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:all'); + }); + + it('should register contracts:dependencies command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:dependencies'); + }); + + it('should register contracts:contract:byTxHash command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:contract:byTxHash'); + }); + + it('should register contracts:reset:dependencies command handler', () => { + contractsManager.events.assert.commandHandlerRegistered('contracts:reset:dependencies'); + }); + }); + + it('should add contract objects', async () => { + const testContract = { + className: 'TestContract' + }; + + await embark.events.request2('contracts:add', testContract); + const contract = await embark.events.request2('contracts:contract', testContract.className); + assert.equal(contract.className, testContract.className); + }); + + it('should return list of added contract objects', async () => { + const testContract = { + className: 'TestContract' + }; + + await embark.events.request2('contracts:add', testContract); + const list = await embark.events.request2('contracts:list'); + assert.equal(list.length, 1); + assert.equal(list[0].className, testContract.className); + }); + + it('should build contracts', async (done) => { + + const contractsConfig = { + contracts: { + deploy: { + TestContract: {} + } + } + }; + + const compiledContracts = { + TestContract: { + // `compiledContract` can't be an empty object otherwise + // contracts-manager will exclude it from processing, resulting + // in `TestContract` not being managed. + code: 'some code', + } + }; + + const beforeBuildAction = sinon.spy((params, cb) => cb(null, params)); + embark.registerActionForEvent('contracts:build:before', beforeBuildAction); + + contractsManager.buildContracts(contractsConfig, compiledContracts, (err, result) => { + assert(beforeBuildAction.calledOnce); + assert.ok(result.TestContract instanceof Contract); + done(); + }); + }); + + it('should return contracts dependencies', async () => { + const contractsConfig = { + contracts: { + deploy: { + TestContract: { + deps: ['Dep1', 'Dep2'] + }, + Dep1: {}, + Dep2: {} + } + } + }; + + const compiledContracts = { + TestContract: { + code: 'foo' + }, + Dep1: {}, + Dep2: {} + }; + + const beforeBuildAction = sinon.spy((params, cb) => { cb(null, params); }); + embark.registerActionForEvent('contracts:build:before', beforeBuildAction); + + await embark.events.request2('contracts:build', contractsConfig, compiledContracts); + const dependencies = await embark.events.request2('contracts:dependencies'); + + assert(dependencies['TestContract']); + assert.equal(dependencies['TestContract'].length, 2); + }); + + describe('API calls', () => { + + describe('GET /embark-api/contract/:contractName', () => { + + const method = 'GET'; + const endpoint = '/embark-api/contract/:contractName'; + + it(`should register ${method} ${endpoint}`, () => { + contractsManager.plugins.assert.apiCallRegistered(method, endpoint); + }) + + it('should return contract by name', async () => { + const TestContract = new Contract(embark.logger, {}); + contractsManager.getContract = sinon.fake.returns(TestContract); + + const response = await contractsManager.plugins.mock.apiCall(method, endpoint, { + params: { + className: 'TestContract' + } + }); + assert(response.send.calledWith(TestContract)); + }); + }); + + describe('GET /embark-api/contracts', () => { + + const method = 'GET'; + const endpoint = '/embark-api/contracts'; + + it(`should register ${method} ${endpoint}`, () => { + contractsManager.plugins.assert.apiCallRegistered(method, endpoint); + }) + + it('should return list of formatted contracts', async () => { + const testContracts = [ + { className: 'TestContract' }, + { className: 'TestContract2' } + ]; + + contractsManager.formatContracts = sinon.fake.returns(testContracts); + contractsManager.getContract = sinon.spy(name => { + return testContracts.find(contract => contract.className = name); + }); + + const response = await contractsManager.plugins.mock.apiCall(method, endpoint); + assert(response.send.calledWith(testContracts)); + }) + }); + + describe('POST /embark-api/contract/:contractName/deploy', () => { + + const method = 'POST'; + const endpoint = '/embark-api/contract/:contractName/deploy'; + + it(`should register ${method} ${endpoint}`, () => { + contractsManager.plugins.assert.apiCallRegistered(method, endpoint); + }) + + it('should deploy contract by name', async () => { + const TestContract = new Contract(embark.logger, { code: 'some code'}); + + function MockContract() { + return { + deploy: (args) => { + return { + estimateGas: () => Promise.resolve(100000), + send: () => Promise.resolve({ _address: 'new address' }) + } + } + } + } + + const Web3Mock = { + eth: { + getAccounts: () => Promise.resolve(['0xfirst']), + Contract: MockContract + } + }; + + contractsManager.getContract = sinon.fake.returns(TestContract); + contractsManager.web3 = Promise.resolve(Web3Mock); + + const response = await contractsManager.plugins.mock.apiCall(method, endpoint, { + contractName: 'TestContract', + inputs: [] + }); + + assert(response.send.calledWith({ result: 'new address' })); + }) + }); + }); +}); diff --git a/packages/stack/contracts-manager/tsconfig.json b/packages/stack/contracts-manager/tsconfig.json index 0520d93f3..6c6409c46 100644 --- a/packages/stack/contracts-manager/tsconfig.json +++ b/packages/stack/contracts-manager/tsconfig.json @@ -21,6 +21,9 @@ }, { "path": "../../core/utils" + }, + { + "path": "../../utils/testing" } ] }