feat(@embark/test-runner): make modules configurable per test

Make storage, ens and communication configurable per test
Tested ENS and it works like a charm
This commit is contained in:
Jonathan Rainville 2019-07-12 12:44:53 -04:00
parent 9648aaf4b1
commit 63ab21ee81
7 changed files with 147 additions and 63 deletions

View File

@ -2,7 +2,9 @@ module.exports = {
"default": {
"enabled": true,
"available_providers": ["ens"],
"provider": "ens",
"provider": "ens"
},
development: {
"register": {
"rootDomain": "embark.eth",
"subdomains": {
@ -11,8 +13,5 @@ module.exports = {
"MyToken2": "$MyToken2"
}
}
},
test: {
enabled: true
}
};

View File

@ -4,6 +4,12 @@ const SimpleStorage = require('Embark/contracts/SimpleStorage');
const EmbarkJS = require('Embark/EmbarkJS');
config({
namesystem: {
enabled: true,
register: {
"rootDomain": "test.eth"
}
},
contracts: {
deploy: {
"SimpleStorage": {
@ -14,12 +20,12 @@ config({
});
describe("EmbarkJS functions", function() {
it('should have access to ENS functions', async function() {
const rootAddress = await EmbarkJS.Names.resolve('embark.eth');
it('should have access to ENS functions and registered test.eth', async function() {
const rootAddress = await EmbarkJS.Names.resolve('test.eth');
assert.strictEqual(rootAddress, web3.eth.defaultAccount);
const rootName = await EmbarkJS.Names.lookup(rootAddress);
assert.strictEqual(rootName, 'embark.eth');
assert.strictEqual(rootName, 'test.eth');
});
it('should have access to Storage functions', async function() {

View File

@ -5,6 +5,15 @@ const MyToken2 = require('Embark/contracts/MyToken2');
const EmbarkJS = require('Embark/EmbarkJS');
config({
namesystem: {
"register": {
"rootDomain": "embark.eth",
"subdomains": {
"mytoken": "$MyToken",
"MyToken2": "$MyToken2"
}
}
},
contracts: {
deploy: {
"Token": {

View File

@ -67,28 +67,39 @@ class ENS {
this.logger = embark.logger;
this.events = embark.events;
this.fs = embark.fs;
this.namesConfig = embark.config.namesystemConfig;
this.config = embark.config;
this.enabled = false;
this.registration = this.namesConfig.register || {};
this.embark = embark;
this.ensConfig = ensConfig;
this.configured = false;
this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, dappArtifacts.symlinkDir);
this.consoleCmdsRegistered = false;
this.eventsRegistered = false;
this.events.setCommandHandler("ens:resolve", this.ensResolve.bind(this));
this.events.setCommandHandler("ens:isENSName", this.isENSName.bind(this));
if (this.namesConfig === {} ||
this.namesConfig.enabled !== true ||
this.namesConfig.available_providers.indexOf('ens') < 0) {
return;
this.embark.events.setCommandHandler("module:namesystem:reset", (cb) => {
this.reset();
this.init(cb);
});
this.init();
}
init(cb = () => {}) {
if (this.config.namesystemConfig === {} ||
this.config.namesystemConfig.enabled !== true ||
!this.config.namesystemConfig.available_providers ||
this.config.namesystemConfig.available_providers.indexOf('ens') < 0) {
return cb();
}
this.enabled = true;
this.doSetENSProvider = this.namesConfig.provider === 'ens';
this.doSetENSProvider = this.config.namesystemConfig.provider === 'ens';
this.addENSToEmbarkJS();
this.registerEvents();
this.registerConsoleCommands();
this.addENSToEmbarkJS(cb);
}
reset() {
@ -96,6 +107,10 @@ class ENS {
}
registerConsoleCommands() {
if (this.consoleCmdsRegistered) {
return;
}
this.consoleCmdsRegistered = true;
this.embark.registerConsoleCommand({
usage: 'resolve [name]',
description: __('Resolves an ENS name'),
@ -138,6 +153,10 @@ class ENS {
}
registerEvents() {
if (this.eventsRegistered) {
return;
}
this.eventsRegistered = true;
this.embark.registerActionForEvent("deploy:beforeAll", this.configureContractsAndRegister.bind(this));
this.events.on('blockchain:reseted', this.reset.bind(this));
this.events.setCommandHandler("storage:ens:associate", this.associateStorageToEns.bind(this));
@ -147,7 +166,7 @@ class ENS {
getEnsConfig(cb) {
cb({
env: this.env,
registration: this.registration,
registration: this.config.namesystemConfig.register,
registryAbi: this.ensConfig.ENSRegistry.abiDefinition,
registryAddress: this.ensConfig.ENSRegistry.deployedAddress,
registrarAbi: this.ensConfig.FIFSRegistrar.abiDefinition,
@ -166,7 +185,7 @@ class ENS {
self.events.request('blockchain:networkId', (networkId) => {
const isKnownNetwork = Boolean(ENS_CONTRACTS_CONFIG[networkId]);
const shouldRegisterSubdomain = self.registration && self.registration.subdomains && Object.keys(self.registration.subdomains).length;
const shouldRegisterSubdomain = self.config.namesystemConfig.register && self.config.namesystemConfig.register.subdomains && Object.keys(self.config.namesystemConfig.register.subdomains).length;
if (isKnownNetwork || !shouldRegisterSubdomain) {
return cb();
}
@ -248,8 +267,8 @@ class ENS {
registerConfigDomains(config, cb) {
this.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
async.each(Object.keys(this.registration.subdomains), (subDomainName, eachCb) => {
const address = this.registration.subdomains[subDomainName];
async.each(Object.keys(this.config.namesystemConfig.register.subdomains), (subDomainName, eachCb) => {
const address = this.config.namesystemConfig.register.subdomains[subDomainName];
const directivesRegExp = new RegExp(/\$(\w+\[?\d?\]?)/g);
@ -281,13 +300,13 @@ class ENS {
}
safeRegisterSubDomain(subDomainName, address, defaultAccount, callback) {
this.ensResolve(`${subDomainName}.${this.registration.rootDomain}`, (error, currentAddress) => {
this.ensResolve(`${subDomainName}.${this.config.namesystemConfig.register.rootDomain}`, (error, currentAddress) => {
if (currentAddress && currentAddress.toLowerCase() === address.toLowerCase()) {
return callback();
}
if (error && error !== NOT_REGISTERED_ERROR) {
this.logger.error(__('Error resolving %s', `${subDomainName}.${this.registration.rootDomain}`));
this.logger.error(__('Error resolving %s', `${subDomainName}.${this.config.namesystemConfig.register.rootDomain}`));
return callback(error);
}
@ -299,7 +318,7 @@ class ENS {
registerSubDomain(defaultAccount, subDomainName, reverseNode, address, secureSend, cb) {
this.events.request("blockchain:get", (web3) => {
ENSFunctions.registerSubDomain(web3, this.ensContract, this.registrarContract, this.resolverContract, defaultAccount,
subDomainName, this.registration.rootDomain, reverseNode, address, this.logger, secureSend, cb, namehash);
subDomainName, this.config.namesystemConfig.register.rootDomain, reverseNode, address, this.logger, secureSend, cb, namehash);
});
}
@ -363,27 +382,30 @@ class ENS {
if (error) {
return res.send({error: error.message || error});
}
res.send({name: `${req.body.subdomain}.${self.registration.rootDomain}`, address: req.body.address});
res.send({name: `${req.body.subdomain}.${self.config.namesystemConfig.register.rootDomain}`, address: req.body.address});
});
});
}
);
}
addENSToEmbarkJS() {
addENSToEmbarkJS(cb) {
this.events.request('version:downloadIfNeeded', 'eth-ens-namehash', (err, location) => {
if (err) {
this.logger.error(__('Error downloading NameHash'));
return this.logger.error(err.message || err);
this.logger.error(err.message || err);
return cb();
}
this.events.request('code-generator:ready', () => {
this.events.request('code-generator:symlink:generate', location, 'eth-ens-namehash', (err) => {
if (err) {
this.logger.error(__('Error creating a symlink to eth-ens-namehash'));
return this.logger.error(err.message || err);
this.logger.error(err.message || err);
return cb();
}
this.events.emit('runcode:register', 'namehash', require('eth-ens-namehash'), () => {
cb();
});
});
});
@ -430,6 +452,7 @@ class ENS {
if (self.configured) {
return cb();
}
const registration = this.config.namesystemConfig.register;
async.waterfall([
function getNetworkId(next) {
@ -454,19 +477,19 @@ class ENS {
});
},
function checkRootNode(next) {
if (!self.registration || !self.registration.rootDomain) {
if (!registration || !registration.rootDomain) {
return next(NO_REGISTRATION);
}
if (!self.isENSName(self.registration.rootDomain)) {
if (!self.isENSName(registration.rootDomain)) {
return next(__('Invalid domain name: {{name}}\nValid extensions are: {{extenstions}}',
{name: self.registration.rootDomain, extenstions: ENS_WHITELIST.join(', ')}));
{name: registration.rootDomain, extenstions: ENS_WHITELIST.join(', ')}));
}
next();
},
function registrar(next) {
const registryAddress = self.ensConfig.ENSRegistry.deployedAddress;
const rootNode = namehash.hash(self.registration.rootDomain);
const rootNode = namehash.hash(registration.rootDomain);
const contract = self.ensConfig.FIFSRegistrar;
contract.args = [registryAddress, rootNode];
@ -518,7 +541,7 @@ class ENS {
self.resolverContract = result[2];
const web3 = result[3];
const rootNode = namehash.hash(self.registration.rootDomain);
const rootNode = namehash.hash(registration.rootDomain);
var reverseNode = namehash.hash(web3.eth.defaultAccount.toLowerCase().substr(2) + reverseAddrSuffix);
const owner = await self.ensContract.methods.owner(rootNode).call();
@ -550,7 +573,7 @@ class ENS {
}, false);
}).then(() => {
// Set name of the reverse node to the root domain
return secureSend(web3, self.resolverContract.methods.setName(reverseNode, self.registration.rootDomain), {
return secureSend(web3, self.resolverContract.methods.setName(reverseNode, registration.rootDomain), {
from: web3.eth.defaultAccount,
gas: ENS_GAS_PRICE
}, false);

View File

@ -4,7 +4,6 @@ import * as async from 'async';
class Storage {
constructor(embark, options){
this.embark = embark;
this.storageConfig = embark.config.storageConfig;
this.plugins = options.plugins;
this.ready = false;
@ -15,22 +14,24 @@ class Storage {
this.embark.events.once("module:storage:ready", cb);
});
if (!this.storageConfig.enabled) {
this.embark.events.setCommandHandler("module:storage:reset", (cb) => {
this.ready = false;
this.addSetProviders(cb);
});
if (!embark.config.storageConfig.enabled) {
this.ready = true;
return;
}
this.handleUploadCommand();
this.addSetProviders(() => {
this.ready = true;
this.embark.events.emit("module:storage:ready");
});
this.addSetProviders(() => {});
}
handleUploadCommand() {
const self = this;
this.embark.events.setCommandHandler('storage:upload', (cb) => {
let platform = self.storageConfig.upload.provider;
let platform = this.embark.config.storageConfig.upload.provider;
let uploadCmds = self.plugins.getPluginsProperty('uploadCmds', 'uploadCmds');
for (let uploadCmd of uploadCmds) {
@ -44,7 +45,7 @@ class Storage {
}
addSetProviders(cb) {
let code = `\nEmbarkJS.Storage.setProviders(${JSON.stringify(this.storageConfig.dappConnection || [])}, {web3});`;
let code = `\nEmbarkJS.Storage.setProviders(${JSON.stringify(this.embark.config.storageConfig.dappConnection || [])}, {web3});`;
let shouldInit = (storageConfig) => {
return storageConfig.enabled;
@ -75,7 +76,10 @@ class Storage {
// in the case where the storage process is too slow when starting up we
// execute ourselves the setProviders because the console provider init
// was already executed
this.embark.events.request('runcode:eval', `if (Object.keys(EmbarkJS.Storage.Providers).length) { ${code} }`, cb, true);
this.embark.events.request('runcode:eval', `if (Object.keys(EmbarkJS.Storage.Providers).length) { ${code} }`, () => {
this.ready = true;
this.embark.events.emit("module:storage:ready");
}, true);
});
}

View File

@ -1,5 +1,6 @@
import { __ } from 'embark-i18n';
import { deconstructUrl, prepareContractsConfig, buildUrl } from 'embark-utils';
import deepEqual from 'deep-equal';
const async = require('async');
const web3Utils = require('web3-utils');
@ -24,7 +25,11 @@ class Test {
this.accounts = [];
this.embarkjs = {};
this.dappPath = options.dappPath;
this.accounts = [];
this.moduleConfigs = {
namesystem: {},
storage: {},
communication: {}
};
this.events.setCommandHandler("blockchain:provider:contract:accounts:get", cb => {
this.events.request("blockchain:getAccounts", cb);
@ -168,6 +173,26 @@ class Test {
});
}
checkModuleConfigs(options, callback) {
const self = this;
const restartModules = [];
Object.keys(this.moduleConfigs).forEach(moduleName => {
options[moduleName] = options[moduleName] || {};
if (!deepEqual(options[moduleName], this.moduleConfigs[moduleName])) {
restartModules.push(function (paraCb) {
self.events.request(`config:${moduleName}Config:set`, options[moduleName], true, () => {
self.events.request(`module:${moduleName}:reset`, paraCb);
});
});
}
});
async.parallel(restartModules, (err, _result) => {
callback(err);
});
}
config(options, callback) {
const self = this;
self.needConfig = false;
@ -198,6 +223,9 @@ class Test {
function checkDeploymentOpts(next) {
self.checkDeploymentOptions(options, next);
},
function checkModuleConfigs(next) {
self.checkModuleConfigs(options, next);
},
function prepareContracts(next) {
if (!self.firstDeployment || !self.options.coverage) {
return next();

View File

@ -32,11 +32,12 @@ const PACKAGE = require('../../../package.json');
const embark5ChangesUrl = 'https://...';
var Config = function(options) {
const self = this;
this.env = options.env || 'default';
this.blockchainConfig = {};
this.contractsConfig = {};
this.pipelineConfig = {};
this.namesystemConfig = {};
this.communicationConfig = {};
this.webServerConfig = options.webServerConfig;
this.chainTracker = {};
this.assetFiles = {};
@ -53,51 +54,65 @@ var Config = function(options) {
this.shownNoAccountConfigMsg = false; // flag to ensure "no account config" message is only displayed once to the user
this.corsParts = [];
this.providerUrl = null;
this.registerEvents();
};
Config.prototype.setConfig = function(configName, newConfig, recursive, cb) {
if (typeof recursive === 'function') {
cb = recursive;
recursive = false;
}
if (recursive) {
this[configName] = recursiveMerge(this[configName], newConfig);
} else {
this[configName] = newConfig;
}
cb();
};
Config.prototype.registerEvents = function() {
this.events.setCommandHandler("config:cors:add", (url) => {
this.corsParts.push(url);
this._updateBlockchainCors();
});
self.events.setCommandHandler("config:contractsConfig", (cb) => {
cb(self.contractsConfig);
this.events.setCommandHandler("config:contractsConfig", (cb) => {
cb(this.contractsConfig);
});
self.events.setCommandHandler("config:contractsConfig:set", (config, cb) => {
self.contractsConfig = config;
cb();
});
this.events.setCommandHandler("config:contractsConfig:set", this.setConfig.bind(this, 'contractsConfig'));
this.events.setCommandHandler("config:blockchainConfig:set", this.setConfig.bind(this, 'blockchainConfig'));
this.events.setCommandHandler("config:storageConfig:set", this.setConfig.bind(this, 'storageConfig'));
this.events.setCommandHandler("config:namesystemConfig:set", this.setConfig.bind(this, 'namesystemConfig'));
this.events.setCommandHandler("config:communicationConfig:set", this.setConfig.bind(this, 'communicationConfig'));
self.events.setCommandHandler("config:blockchainConfig:set", (config, cb) => {
self.blockchainConfig = config;
cb();
});
self.events.setCommandHandler("config:contractsFiles", (cb) => {
cb(self.contractsFiles);
this.events.setCommandHandler("config:contractsFiles", (cb) => {
cb(this.contractsFiles);
});
// TODO: refactor this so reading the file can be done with a normal resolver or something that takes advantage of the plugin api
self.events.setCommandHandler("config:contractsFiles:add", (filename, resolver) => {
this.events.setCommandHandler("config:contractsFiles:add", (filename, resolver) => {
resolver = resolver || function(callback) {
callback(fs.readFileSync(filename).toString());
};
self.contractsFiles.push(new File({path: filename, originalPath: filename, type: Types.custom, resolver}));
this.contractsFiles.push(new File({path: filename, originalPath: filename, type: Types.custom, resolver}));
});
self.events.setCommandHandler("config:contractsFiles:reset", (cb) => {
self.contractsFiles.forEach((file) => {
this.events.setCommandHandler("config:contractsFiles:reset", (cb) => {
this.contractsFiles.forEach((file) => {
if(file.path.includes(".embark")) {
fs.removeSync(file.path);
}
self.contractsFiles = self.contractsFiles.filter((contractFile) => contractFile.path !== file.path);
this.contractsFiles = this.contractsFiles.filter((contractFile) => contractFile.path !== file.path);
});
cb();
});
self.events.on('file-remove', (fileType, removedPath) => {
this.events.on('file-remove', (fileType, removedPath) => {
if(fileType !== 'contract') return;
const normalizedPath = path.normalize(removedPath);
self.contractsFiles = self.contractsFiles.filter(file => path.normalize(file.path) !== normalizedPath);
this.contractsFiles = this.contractsFiles.filter(file => path.normalize(file.path) !== normalizedPath);
});
};