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
+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();
});
});
});