Add basic mock provider (#58)
This commit is contained in:
@@ -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)
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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');
|
||||
};
|
||||
@@ -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();
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user