Add basic mock provider (#58)

This commit is contained in:
Jaco Greeff
2018-03-16 12:10:13 +01:00
committed by GitHub
parent 030bb409f1
commit 913d6d8bd0
12 changed files with 410 additions and 17 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/dev": "^0.16.17",
"@polkadot/dev": "^0.16.19",
"lerna": "^2.5.1"
}
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { ProviderInterface } from '../types';
const mocks = require('./mocks');
const send = require('./send');
const state = require('./state');
const subscribe = require('./subscribe');
const unsubscribe = require('./unsubscribe');
module.exports = function mockProvider (): ProviderInterface {
const self = state();
mocks(self);
return {
isConnected: (): boolean =>
true,
send: (method: string, params: Array<mixed>): Promise<mixed> =>
send(self, method, params),
subscribe: (method: string, ...params: Array<mixed>): Promise<number> =>
subscribe(self, method, params),
unsubscribe: (id: number): Promise<boolean> =>
unsubscribe(self, id)
};
};
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { MockState, MockState$Subscription$Callback } from './types';
module.exports = function mocks ({ subscriptions }: MockState): void {
let blockNumber = 0;
const updateSubs = (method, value) => {
subscriptions[method].lastValue = value;
Object
.values(subscriptions[method].callbacks)
.forEach((cb) => {
try {
// flowlint-next-line unclear-type:off
((cb: any): MockState$Subscription$Callback)(null, value);
} catch (error) {
console.error(`Error on '${method}' subscription`, error);
}
});
};
setInterval(() => {
updateSubs('extra_getBlockNumber', `0x${(++blockNumber).toString(16)}`);
}, 5000);
setInterval(() => {
updateSubs('extra_getClientTime', `0x${Date.now().toString(16)}`);
}, 1000);
};
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { MockState } from './types';
module.exports = async function send (self: MockState, method: string, params: Array<mixed>): Promise<mixed> {
throw new Error('provider.send not implemented');
};
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { MockState } from './types';
const interfaces = require('@polkadot/api-jsonrpc');
const l = require('@polkadot/util/logger')('api-mock');
const METHODS = Array.prototype.concat.apply(
[], Object.keys(interfaces).map((section) => {
return Object
.keys(interfaces[section].methods)
.map((method) => `${section}_${method}`);
})
);
module.exports = function state (): MockState {
const subscriptions = METHODS.reduce((subscriptions, name) => {
subscriptions[name] = {
callbacks: {},
lastValue: null
};
return subscriptions;
}, {});
return {
l,
subscriptionId: 0,
subscriptionMap: {},
subscriptions
};
};
@@ -0,0 +1,33 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
const createState = require('./state');
describe('state', () => {
let state;
beforeEach(() => {
state = createState();
});
it('generates a sane initial state', () => {
expect(
state
).toMatchObject({
subscriptionId: 0,
subscriptionMap: {}
});
});
it('generates a proper section_method map', () => {
expect(
state.subscriptions
).toMatchObject({
'extra_getBlockNumber': {
callbacks: {},
lastValue: null
}
});
});
});
@@ -0,0 +1,25 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { MockState, MockState$Subscription$Callback } from './types';
module.exports = async function subscribe (self: MockState, method: string, params: Array<mixed>): Promise<number> {
self.l.debug(() => ['subscribe', method, params]);
if (self.subscriptions[method]) {
// flowlint-next-line unclear-type:off
const callback = ((params.pop(): any): MockState$Subscription$Callback);
const id = ++self.subscriptionId;
self.subscriptions[method].callbacks[id] = callback;
self.subscriptionMap[id] = method;
callback(null, self.subscriptions[method].lastValue);
return id;
}
throw new Error(`provider.subscribe: Invalid method '${method}'`);
};
@@ -0,0 +1,44 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
const createState = require('./state');
const subscribe = require('./subscribe');
describe('subscribe', () => {
let state;
beforeEach(() => {
state = createState();
});
it('fails on unknown methods', () => {
return subscribe(state, 'test_notFound').catch((error) => {
expect(error.message).toMatch(/Invalid method 'test_notFound'/);
});
});
it('returns a subscription id', () => {
return subscribe(state, 'extra_getBlockNumber', [() => void 0]).then((id) => {
expect(id).toEqual(state.subscriptionId);
});
});
it('stores the mapping values', () => {
const cb = () => void 0;
return subscribe(state, 'extra_getBlockNumber', [cb]).then((id) => {
expect(state.subscriptionMap[id]).toEqual('extra_getBlockNumber');
expect(state.subscriptions['extra_getBlockNumber'].callbacks[id]).toEqual(cb);
});
});
it('calls back with the last known value', (done) => {
state.subscriptions['extra_getBlockNumber'].lastValue = 'testValue';
subscribe(state, 'extra_getBlockNumber', [(_, value) => {
expect(value).toEqual('testValue');
done();
}]);
});
});
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Logger } from '@polkadot/util/types';
export type MockState$Subscription$Callback = (error: ?Error, value: mixed) => void;
export type MockState$Subscriptions = {
[string]: {
callbacks: {
[number]: MockState$Subscription$Callback
},
lastValue: mixed
}
};
export type MockState = {
l: Logger,
subscriptionId: number,
subscriptionMap: {
[number]: string
},
subscriptions: MockState$Subscriptions
};
@@ -0,0 +1,21 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { MockState } from './types';
module.exports = async function subscribe (self: MockState, id: number): Promise<boolean> {
const method = self.subscriptionMap[id];
self.l.debug(() => ['unsubscribe', id, method]);
if (!method) {
throw new Error(`Unable to find subscription for ${id}`);
}
delete self.subscriptionMap[id];
delete self.subscriptions[method].callbacks[id];
return true;
};
@@ -0,0 +1,45 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
const createState = require('./state');
const subscribe = require('./subscribe');
const unsubscribe = require('./unsubscribe');
describe('unsubscribe', () => {
let state;
let id;
beforeEach(() => {
state = createState();
return subscribe(state, 'extra_getBlockNumber', [() => void 0]).then((_id) => {
id = _id;
});
});
it('fails on unknown ids', () => {
return unsubscribe(state, 5).catch((error) => {
expect(error.message).toMatch(/Unable to find/);
});
});
it('unsubscribes successfully', () => {
return unsubscribe(state, id);
});
it('fails on double unsubscribe', () => {
return unsubscribe(state, id)
.then(() => unsubscribe(state, id))
.catch((error) => {
expect(error.message).toMatch(/Unable to find/);
});
});
it('clears the subscriptions', () => {
return unsubscribe(state, id).then(() => {
expect(state.subscriptionMap[id]).not.toBeDefined();
expect(state.subscriptions['extra_getBlockNumber'].callbacks[id]).not.toBeDefined();
});
});
});
+108 -16
View File
@@ -79,9 +79,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@polkadot/dev@^0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.16.17.tgz#530653a5659a9d6a0784aeed99b219ee3a56d340"
"@polkadot/dev@^0.16.19":
version "0.16.19"
resolved "https://registry.yarnpkg.com/@polkadot/dev/-/dev-0.16.19.tgz#9d3158c43bd75688428e264e47937b838a0560af"
dependencies:
babel-cli "^6.26.0"
babel-core "^6.26.0"
@@ -91,6 +91,7 @@
babel-plugin-transform-object-rest-spread "^6.26.0"
babel-plugin-transform-runtime "^6.23.0"
babel-preset-env "^1.6.1"
codeclimate-test-reporter "^0.5.0"
coveralls "^3.0.0"
dox "^0.9.0"
eslint "^4.18.2"
@@ -105,7 +106,7 @@
eslint-plugin-promise "^3.6.0"
eslint-plugin-react "^7.7.0"
eslint-plugin-standard "^3.0.1"
flow-bin "^0.67.1"
flow-bin "^0.68.0"
flow-copy-source "^1.3.0"
jest "^22.4.2"
makeshift "^1.1.0"
@@ -441,7 +442,7 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
async@^1.4.0, async@^1.5.0:
async@^1.4.0, async@^1.5.0, async@~1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -1258,6 +1259,10 @@ capture-stack-trace@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
caseless@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1281,7 +1286,7 @@ center-align@^0.1.1:
deep-eql "^0.1.3"
type-detect "^1.0.0"
chalk@^1.1.3:
chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -1437,6 +1442,15 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
codeclimate-test-reporter@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/codeclimate-test-reporter/-/codeclimate-test-reporter-0.5.0.tgz#93fa06b1c18e4117349128dc4e38aad08043828e"
dependencies:
async "~1.5.2"
commander "2.9.0"
lcov-parse "0.0.10"
request "~2.79.0"
collapse-white-space@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c"
@@ -1475,16 +1489,20 @@ command-join@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf"
commander@^2.11.0:
version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
commander@~2.9.0:
commander@2.9.0, commander@~2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
dependencies:
graceful-readlink ">= 1.0.0"
commander@^2.11.0:
version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
commander@^2.9.0:
version "2.15.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322"
compare-func@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648"
@@ -2444,9 +2462,9 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
flow-bin@^0.67.1:
version "0.67.1"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.67.1.tgz#eabb7197cce870ac9442cfd04251c7ddc30377db"
flow-bin@^0.68.0:
version "0.68.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.68.0.tgz#86c2d14857d306eb2e85e274f2eebf543564f623"
flow-copy-source@^1.3.0:
version "1.3.0"
@@ -2567,6 +2585,16 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
generate-function@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
generate-object-property@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
dependencies:
is-property "^1.0.0"
get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
@@ -2768,6 +2796,15 @@ har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
har-validator@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
dependencies:
chalk "^1.1.1"
commander "^2.9.0"
is-my-json-valid "^2.12.4"
pinkie-promise "^2.0.0"
har-validator@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
@@ -3159,6 +3196,20 @@ is-hexadecimal@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69"
is-my-ip-valid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
is-my-json-valid@^2.12.4:
version "2.17.2"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c"
dependencies:
generate-function "^2.0.0"
generate-object-property "^1.1.0"
is-my-ip-valid "^1.0.0"
jsonpointer "^4.0.0"
xtend "^4.0.0"
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -3223,6 +3274,10 @@ is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
is-property@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
is-redirect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
@@ -3754,6 +3809,10 @@ jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -3815,7 +3874,7 @@ lcid@^1.0.0:
dependencies:
invert-kv "^1.0.0"
lcov-parse@^0.0.10:
lcov-parse@0.0.10, lcov-parse@^0.0.10:
version "0.0.10"
resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3"
@@ -4811,6 +4870,10 @@ qs@^6.5.1, qs@~6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
qs@~6.3.0:
version "6.3.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
qs@~6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@@ -5128,6 +5191,31 @@ request@^2.79.0, request@^2.83.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@~2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
dependencies:
aws-sign2 "~0.6.0"
aws4 "^1.2.1"
caseless "~0.11.0"
combined-stream "~1.0.5"
extend "~3.0.0"
forever-agent "~0.6.1"
form-data "~2.1.1"
har-validator "~2.0.6"
hawk "~3.1.3"
http-signature "~1.1.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.7"
oauth-sign "~0.8.1"
qs "~6.3.0"
stringstream "~0.0.4"
tough-cookie "~2.3.0"
tunnel-agent "~0.4.1"
uuid "^3.0.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -5853,6 +5941,10 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel-agent@~0.4.1:
version "0.4.3"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -6215,7 +6307,7 @@ xml-name-validator@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
xtend@^4.0.1, xtend@~4.0.1:
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"