Skip to content

Commit

Permalink
Tests completed.
Browse files Browse the repository at this point in the history
  • Loading branch information
kukabi committed Sep 14, 2023
1 parent a59ac75 commit 3472ad2
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 20 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testTimeout: 15_000,
testTimeout: 30_000,
};
38 changes: 22 additions & 16 deletions src/client/data/data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ class DataStore {
* @param header new block header
* @param done completion callback
*/
private async processNewBlock(header: Header, done: AsyncLock.AsyncLockDoneCallback<unknown>) {
private async processNewBlock(header: Header, done?: AsyncLock.AsyncLockDoneCallback<unknown>) {
if (
this.blocks.findIndex((block) => block.block.header.toHex() == header.hash.toHex()) >= 0
) {
Expand All @@ -450,7 +450,9 @@ class DataStore {
while (this.blocks.length > Constants.MAX_BLOCK_COUNT) {
this.eventBus.dispatch<Block>(ChainvizEvent.DISCARDED_BLOCK, this.blocks.pop());
}
done();
if (done) {
done();
}
}

/**
Expand Down Expand Up @@ -481,7 +483,7 @@ class DataStore {
*/
private async processFinalizedBlock(
header: Header,
done: AsyncLock.AsyncLockDoneCallback<unknown>,
done?: AsyncLock.AsyncLockDoneCallback<unknown>,
) {
// find unfinalized blocks before this one & discard & finalize
const removeIndices: number[] = [];
Expand All @@ -499,18 +501,20 @@ class DataStore {
this.eventBus.dispatch<Block>(ChainvizEvent.DISCARDED_BLOCK, removed[0]);
}

let number = header.number.toNumber() - 1;
while (
this.blocks.findIndex((block) => block.block.header.number.toNumber() == number) < 0
) {
const block = await this.getBlockByNumber(number);
if (block) {
block.isFinalized = true;
this.insertBlock(block);
this.eventBus.dispatch<Block>(ChainvizEvent.FINALIZED_BLOCK, block);
number--;
} else {
break;
if (this.blocks.length > 0) {
let number = header.number.toNumber() - 1;
while (
this.blocks.findIndex((block) => block.block.header.number.toNumber() == number) < 0
) {
const block = await this.getBlockByNumber(number);
if (block) {
block.isFinalized = true;
this.insertBlock(block);
this.eventBus.dispatch<Block>(ChainvizEvent.FINALIZED_BLOCK, block);
number--;
} else {
break;
}
}
}

Expand All @@ -528,7 +532,9 @@ class DataStore {
this.eventBus.dispatch<Block>(ChainvizEvent.FINALIZED_BLOCK, block);
}
}
done();
if (done) {
done();
}
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/client/service/rpc/rpc-subscription-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReconnectingWebSocket from 'reconnecting-websocket';
import camelcaseKeysDeep from 'camelcase-keys-deep';
import ws from 'ws';

/**
* JSON-RPC 2.0 request.
Expand Down Expand Up @@ -79,7 +80,7 @@ class RPCSubscriptionService<T> {
private onMessage(event: MessageEvent) {
const json = JSON.parse(event.data);
if (Object.prototype.hasOwnProperty.call(json, 'result')) {
if (isNaN(json['result'])) {
if (isNaN(parseFloat(json['result']))) {
this.state = RPCSubscriptionServiceState.Connected;
this.listener.onUnsubscribed(this.subscriptionId);
this.subscriptionId = 0;
Expand All @@ -102,7 +103,7 @@ class RPCSubscriptionService<T> {

connect() {
this.connection = new ReconnectingWebSocket(this.url, [], {
// WebSocket: WebSocket,
WebSocket: ws,
connectionTimeout: 5000,
});
this.connection.onopen = () => {
Expand Down
6 changes: 5 additions & 1 deletion src/client/util/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ function formatNumber(
}

const decimalPart = formatted.substring(formatted.length - formatDecimals);
formatted = `${integerPart}${Constants.DECIMAL_SEPARATOR}${decimalPart}`;
if (decimalPart.length > 0) {
formatted = `${integerPart}${Constants.DECIMAL_SEPARATOR}${decimalPart}`;
} else {
formatted = integerPart;
}
if (ticker) {
return `${formatted} ${ticker}`;
} else {
Expand Down
125 changes: 125 additions & 0 deletions tests/data-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,129 @@ describe('data store', () => {
expect(block!.events.length).toBe(44);
await dataStore.disconnectSubstrateClient();
});
test('inserts blocks at correct indices', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const block10 = await dataStore.getBlockByNumber(10);
dataStore['insertBlock'](block10!);
const block30 = await dataStore.getBlockByNumber(30);
dataStore['insertBlock'](block30!);
const block20 = await dataStore.getBlockByNumber(20);
dataStore['insertBlock'](block20!);
const block15 = await dataStore.getBlockByNumber(15);
dataStore['insertBlock'](block15!);
expect(dataStore['blocks'][0].block.header.number.toNumber()).toBe(30);
expect(dataStore['blocks'][1].block.header.number.toNumber()).toBe(20);
expect(dataStore['blocks'][2].block.header.number.toNumber()).toBe(15);
expect(dataStore['blocks'][3].block.header.number.toNumber()).toBe(10);
});
test('discards older blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const initialBlockNumber = 10;
const excessBlockCount = 5;
for (let i = 0; i < Constants.MAX_BLOCK_COUNT + 5; i++) {
const block = await dataStore.getBlockByNumber(initialBlockNumber + i);
dataStore['insertBlock'](block!);
}
for (let i = 0; i < excessBlockCount; i++) {
const block = await dataStore.getBlockByNumber(10 + Constants.MAX_BLOCK_COUNT + i);
dataStore['processNewBlock'](block!.block.header);
}
expect(dataStore['blocks'].length).toBe(Constants.MAX_BLOCK_COUNT);
});
test('gets missing finalized blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const eventBus = EventBus.getInstance();
let finalizedBlockEventCount = 0;
eventBus.register(ChainvizEvent.FINALIZED_BLOCK, (_block: Block) => {
finalizedBlockEventCount++;
});
const initialBlockNumber = 10;
const firstBlock = await dataStore.getBlockByNumber(initialBlockNumber);
firstBlock!.isFinalized = true;
dataStore['insertBlock'](firstBlock!);
const lastBlock = await dataStore.getBlockByNumber(initialBlockNumber + 5);
await dataStore['processFinalizedBlock'](lastBlock!.block.header);
await new Promise((resolve) => setTimeout(resolve, 2_000));
expect(finalizedBlockEventCount).toBe(5);
});
test('removes unfinalized blocks and replaces with finalized blocks', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const eventBus = EventBus.getInstance();
let discardedBlockEventCount = 0;
eventBus.register(ChainvizEvent.DISCARDED_BLOCK, (_block: Block) => {
discardedBlockEventCount++;
});
let finalizedBlockEventCount = 0;
eventBus.register(ChainvizEvent.FINALIZED_BLOCK, (_block: Block) => {
finalizedBlockEventCount++;
});
const block10 = await dataStore.getBlockByNumber(10);
block10!.isFinalized = true;
dataStore['insertBlock'](block10!);
const block11 = await dataStore.getBlockByNumber(11);
dataStore['insertBlock'](block11!);
const block12 = await dataStore.getBlockByNumber(12);
dataStore['insertBlock'](block12!);
const block13 = await dataStore.getBlockByNumber(13);
await dataStore['processFinalizedBlock'](block13!.block.header);
await new Promise((resolve) => setTimeout(resolve, 2_000));
expect(discardedBlockEventCount).toBe(2);
expect(finalizedBlockEventCount).toBe(3);
});
test('only removes unfinalized blocks if no finalized blocks prior', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.connectSubstrateRPC();
const eventBus = EventBus.getInstance();
let discardedBlockEventCount = 0;
eventBus.register(ChainvizEvent.DISCARDED_BLOCK, (_block: Block) => {
discardedBlockEventCount++;
});
let finalizedBlockEventCount = 0;
eventBus.register(ChainvizEvent.FINALIZED_BLOCK, (_block: Block) => {
finalizedBlockEventCount++;
});
const block10 = await dataStore.getBlockByNumber(10);
dataStore['insertBlock'](block10!);
const block11 = await dataStore.getBlockByNumber(11);
dataStore['insertBlock'](block11!);
const block12 = await dataStore.getBlockByNumber(12);
dataStore['insertBlock'](block12!);
const block13 = await dataStore.getBlockByNumber(13);
await dataStore['processFinalizedBlock'](block13!.block.header);
await new Promise((resolve) => setTimeout(resolve, 2_000));
expect(discardedBlockEventCount).toBe(3);
expect(finalizedBlockEventCount).toBe(1);
});
test('can get XCM transfers', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.getXCMTransfers();
expect(dataStore.xcmTransfers.length).toBeGreaterThan(0);
expect(dataStore.xcmTransfers.length).toBeLessThanOrEqual(Constants.XCM_DISPLAY_LIMIT);
for (const xcmTransfer of dataStore.xcmTransfers) {
expect(xcmTransfer.relayChain.relayChain).toBe(Kusama.id);
expect(xcmTransfer.origination).toBeDefined();
expect(xcmTransfer.destination).toBeDefined();
}
clearTimeout(dataStore['xcmTransferGetTimeout']);
});
test('can get XCM transfer by origin hash', async () => {
const dataStore = new DataStore();
await dataStore.setNetwork(Kusama);
await dataStore.getXCMTransfers();
const xcmTransfer = dataStore.getXCMTransferByOriginExtrinsicHash(
dataStore.xcmTransfers[0].origination.extrinsicHash,
);
expect(xcmTransfer).toBeDefined();
clearTimeout(dataStore['xcmTransferGetTimeout']);
});
});
27 changes: 27 additions & 0 deletions tests/event-bus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EventBus } from '../src/client/event/event-bus';
import { describe, expect, test } from '@jest/globals';

interface EventObject {
a: number;
b: string;
c: bigint;
}

describe('event bus', () => {
test('pub/sub works', async () => {
const eventBus = EventBus.getInstance();
const eventName = 'some_event';
const eventObject: EventObject = {
a: 1,
b: 'two',
c: 3n,
};
let eventReceived = false;
eventBus.register(eventName, (receivedEventObject: EventObject) => {
eventReceived = eventObject == receivedEventObject;
});
eventBus.dispatch(eventName, eventObject);
await new Promise((resolve) => setTimeout(resolve, 500));
expect(eventReceived).toBeTruthy();
});
});
18 changes: 18 additions & 0 deletions tests/format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Kusama } from '../src/client/model/substrate/network';
import { formatNumber } from '../src/client/util/format';
import { describe, expect, test } from '@jest/globals';

describe('format', () => {
test('number formatting works with ticker', async () => {
const formatted = formatNumber(123456789n, 5, 2, Kusama.tokenTicker);
expect(formatted).toBe('1,234.56 KSM');
});
test('number formatting works without ticker', async () => {
const formatted = formatNumber(123456789n, 5, 2);
expect(formatted).toBe('1,234.56');
});
test('number formatting works with no format decimals', async () => {
const formatted = formatNumber(123456789n, 5, 0);
expect(formatted).toBe('1,234');
});
});
13 changes: 13 additions & 0 deletions tests/identicon.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, test } from '@jest/globals';
import { generateIdenticonSVGHTML } from '../src/client/util/identicon';

describe('identicon', () => {
test('generates correct identicon', async () => {
const expectedSVG = `<svg style="width; 25; height: 25;" viewBox='0 0 64 64'><circle cx=32 cy=32 fill="#eee" r=32 /><circle cx=32 cy=8 fill="hsl(196, 62%, 53%)" r=5 /><circle cx=32 cy=20 fill="hsl(191, 62%, 53%)" r=5 /><circle cx=21.607695154586736 cy=14 fill="hsl(219, 62%, 35%)" r=5 /><circle cx=11.215390309173472 cy=20 fill="hsl(33, 62%, 15%)" r=5 /><circle cx=21.607695154586736 cy=26 fill="hsl(118, 62%, 15%)" r=5 /><circle cx=11.215390309173472 cy=32 fill="hsl(157, 62%, 53%)" r=5 /><circle cx=11.215390309173472 cy=44 fill="hsl(202, 62%, 35%)" r=5 /><circle cx=21.607695154586736 cy=38 fill="hsl(191, 62%, 53%)" r=5 /><circle cx=21.607695154586736 cy=50 fill="hsl(67, 62%, 35%)" r=5 /><circle cx=32 cy=56 fill="hsl(50, 62%, 15%)" r=5 /><circle cx=32 cy=44 fill="hsl(129, 62%, 75%)" r=5 /><circle cx=42.392304845413264 cy=50 fill="hsl(67, 62%, 35%)" r=5 /><circle cx=52.78460969082653 cy=44 fill="hsl(202, 62%, 35%)" r=5 /><circle cx=42.392304845413264 cy=38 fill="hsl(191, 62%, 53%)" r=5 /><circle cx=52.78460969082653 cy=32 fill="hsl(157, 62%, 53%)" r=5 /><circle cx=52.78460969082653 cy=20 fill="hsl(33, 62%, 15%)" r=5 /><circle cx=42.392304845413264 cy=26 fill="hsl(118, 62%, 15%)" r=5 /><circle cx=42.392304845413264 cy=14 fill="hsl(219, 62%, 35%)" r=5 /><circle cx=32 cy=32 fill="hsl(95, 62%, 75%)" r=5 /></svg>`;
const generatedSVG = generateIdenticonSVGHTML(
'21vLqCuvXweuKw9nw6qfAQnFkmBnxLWA3RU5cMBGuzsdEJ4A',
25,
);
expect(generatedSVG).toBe(expectedSVG);
});
});
19 changes: 19 additions & 0 deletions tests/object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, test } from '@jest/globals';
import { cloneJSONSafeObject } from '../src/client/util/object';
import { validators } from './data/validators';

// prettier-ignore
(BigInt.prototype as any).toJSON = function () { // eslint-disable-line @typescript-eslint/no-explicit-any
return this.toString();
};

describe('object', () => {
test('clone object works', async () => {
const cloneValidator1 = cloneJSONSafeObject(validators[0]);
expect(JSON.stringify(cloneValidator1)).toEqual(JSON.stringify(validators[0]));
});
test('clone array works', async () => {
const cloneValidators = cloneJSONSafeObject(validators);
expect(JSON.stringify(cloneValidators)).toEqual(JSON.stringify(validators));
});
});
Loading

0 comments on commit 3472ad2

Please sign in to comment.