@cardano-sdk/e2e 0.36.7 → 0.36.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -1
- package/CHANGELOG.md +11 -0
- package/dist/cjs/environment.d.ts +3 -2
- package/dist/cjs/environment.d.ts.map +1 -1
- package/dist/cjs/environment.js +3 -1
- package/dist/cjs/environment.js.map +1 -1
- package/dist/cjs/factories.d.ts.map +1 -1
- package/dist/cjs/factories.js +10 -1
- package/dist/cjs/factories.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/environment.d.ts +3 -2
- package/dist/esm/environment.d.ts.map +1 -1
- package/dist/esm/environment.js +3 -1
- package/dist/esm/environment.js.map +1 -1
- package/dist/esm/factories.d.ts.map +1 -1
- package/dist/esm/factories.js +10 -1
- package/dist/esm/factories.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/docker-compose.yml +5 -0
- package/jest.config.js +1 -0
- package/package.json +23 -21
- package/src/environment.ts +3 -1
- package/src/factories.ts +18 -5
- package/test/k6/scenarios/web-socket.test.js +70 -0
- package/test/long-running/webSocket.test.ts +25 -0
- package/test/ws-server/webSocket.test.ts +220 -0
package/docker-compose.yml
CHANGED
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardano-sdk/e2e",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.9",
|
|
4
4
|
"description": "End to end tests for the cardano-js-sdk packages.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16.20.2"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"test:ogmios": "jest -c jest.config.js --forceExit --selectProjects ogmios --runInBand --verbose",
|
|
32
32
|
"test:pg-boss": "jest -c jest.config.js --forceExit --selectProjects pg-boss --runInBand --verbose",
|
|
33
33
|
"test:providers": "jest -c jest.config.js --forceExit --selectProjects providers --runInBand --verbose",
|
|
34
|
-
"test:wallet": "yarn wait-for-network-init ; yarn test:wallet:epoch0 && { yarn wait-for-network-epoch-3 ; yarn test:wallet:epoch3 ; }",
|
|
34
|
+
"test:wallet": "yarn wait-for-network-init ; yarn test:wallet:epoch0 && yarn test:ws && { yarn wait-for-network-epoch-3 ; yarn test:wallet:epoch3 ; }",
|
|
35
35
|
"test:wallet:epoch0": "jest -c jest.config.js --forceExit --selectProjects wallet_epoch_0 --runInBand --verbose",
|
|
36
36
|
"test:wallet:epoch3": "jest -c jest.config.js --forceExit --selectProjects wallet_epoch_3 --runInBand --verbose",
|
|
37
37
|
"test:wallet-real-ada": "NETWORK_SPEED=slow jest -c jest.config.js --forceExit --selectProjects wallet-real-ada --runInBand --verbose",
|
|
@@ -46,8 +46,10 @@
|
|
|
46
46
|
"test:web-extension:watch:run": "yarn test:web-extension:run --watch",
|
|
47
47
|
"test:web-extension:watch": "run-s test:web-extension:build test:web-extension:watch:bg",
|
|
48
48
|
"test:web-extension:watch:bg": "run-p test:web-extension:watch:build test:web-extension:watch:run",
|
|
49
|
+
"test:ws": "jest -c jest.config.js --forceExit --selectProjects ws-server --runInBand --verbose",
|
|
49
50
|
"local-network:common": "PRE_CONWAY=${PRE_CONWAY:-stable} DISABLE_DB_CACHE=${DISABLE_DB_CACHE:-true} SUBMIT_API_ARGS='--testnet-magic 888' USE_BLOCKFROST=false __FIX_UMASK__=$(chmod -R a+r ../../compose/placeholder-secrets) docker compose --env-file ../cardano-services/environments/.env.local -p local-network-e2e -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/$(uname -m).yml $FILES up",
|
|
50
51
|
"local-network:up": "FILES='' yarn local-network:common",
|
|
52
|
+
"local-network:single:up": "FILES='' yarn local-network:common cardano-node file-server local-testnet ogmios postgres",
|
|
51
53
|
"local-network:profile:up": "FILES='-f ../../compose/pg-agent.yml' yarn local-network:common",
|
|
52
54
|
"local-network:down": "docker compose -p local-network-e2e -f docker-compose.yml -f ../../compose/common.yml -f ../../compose/pg-agent.yml down -v --remove-orphans",
|
|
53
55
|
"cardano-services:up": "ts-node --transpile-only ../cardano-services/src/cli.ts start-provider-server",
|
|
@@ -79,20 +81,20 @@
|
|
|
79
81
|
"dependencies": {
|
|
80
82
|
"@cardano-foundation/ledgerjs-hw-app-cardano": "^7.1.2",
|
|
81
83
|
"@cardano-ogmios/client": "6.3.0",
|
|
82
|
-
"@cardano-sdk/cardano-services": "~0.28.
|
|
83
|
-
"@cardano-sdk/cardano-services-client": "~0.19.
|
|
84
|
-
"@cardano-sdk/core": "~0.35.
|
|
85
|
-
"@cardano-sdk/crypto": "~0.1.
|
|
86
|
-
"@cardano-sdk/hardware-ledger": "~0.
|
|
87
|
-
"@cardano-sdk/hardware-trezor": "~0.4.
|
|
88
|
-
"@cardano-sdk/input-selection": "~0.13.
|
|
89
|
-
"@cardano-sdk/key-management": "~0.
|
|
90
|
-
"@cardano-sdk/ogmios": "~0.15.
|
|
91
|
-
"@cardano-sdk/tx-construction": "~0.19.
|
|
92
|
-
"@cardano-sdk/util": "~0.15.
|
|
93
|
-
"@cardano-sdk/util-dev": "~0.21.
|
|
94
|
-
"@cardano-sdk/util-rxjs": "~0.7.
|
|
95
|
-
"@cardano-sdk/wallet": "~0.
|
|
84
|
+
"@cardano-sdk/cardano-services": "~0.28.14",
|
|
85
|
+
"@cardano-sdk/cardano-services-client": "~0.19.14",
|
|
86
|
+
"@cardano-sdk/core": "~0.35.4",
|
|
87
|
+
"@cardano-sdk/crypto": "~0.1.28",
|
|
88
|
+
"@cardano-sdk/hardware-ledger": "~0.10.1",
|
|
89
|
+
"@cardano-sdk/hardware-trezor": "~0.4.35",
|
|
90
|
+
"@cardano-sdk/input-selection": "~0.13.8",
|
|
91
|
+
"@cardano-sdk/key-management": "~0.21.1",
|
|
92
|
+
"@cardano-sdk/ogmios": "~0.15.35",
|
|
93
|
+
"@cardano-sdk/tx-construction": "~0.19.8",
|
|
94
|
+
"@cardano-sdk/util": "~0.15.4",
|
|
95
|
+
"@cardano-sdk/util-dev": "~0.21.8",
|
|
96
|
+
"@cardano-sdk/util-rxjs": "~0.7.21",
|
|
97
|
+
"@cardano-sdk/wallet": "~0.39.1",
|
|
96
98
|
"@dcspark/cardano-multiplatform-lib-nodejs": "^3.1.1",
|
|
97
99
|
"@vespaiach/axios-fetch-adapter": "^0.3.0",
|
|
98
100
|
"axios": "^0.28.0",
|
|
@@ -122,10 +124,10 @@
|
|
|
122
124
|
"@babel/core": "^7.18.2",
|
|
123
125
|
"@babel/preset-env": "^7.18.2",
|
|
124
126
|
"@babel/preset-typescript": "^7.17.12",
|
|
125
|
-
"@cardano-sdk/dapp-connector": "~0.12.
|
|
126
|
-
"@cardano-sdk/projection": "~0.11.
|
|
127
|
-
"@cardano-sdk/projection-typeorm": "~0.8.
|
|
128
|
-
"@cardano-sdk/web-extension": "~0.29.
|
|
127
|
+
"@cardano-sdk/dapp-connector": "~0.12.26",
|
|
128
|
+
"@cardano-sdk/projection": "~0.11.25",
|
|
129
|
+
"@cardano-sdk/projection-typeorm": "~0.8.27",
|
|
130
|
+
"@cardano-sdk/web-extension": "~0.29.9",
|
|
129
131
|
"@dcspark/cardano-multiplatform-lib-browser": "^3.1.1",
|
|
130
132
|
"@emurgo/cardano-message-signing-asmjs": "^1.0.1",
|
|
131
133
|
"@types/bunyan": "^1.8.8",
|
|
@@ -179,5 +181,5 @@
|
|
|
179
181
|
"webpack-cli": "^4.9.2",
|
|
180
182
|
"webpack-merge": "^5.8.0"
|
|
181
183
|
},
|
|
182
|
-
"gitHead": "
|
|
184
|
+
"gitHead": "8251245efc792b61a6a5c2be4b42fcbaeaf2312a"
|
|
183
185
|
}
|
package/src/environment.ts
CHANGED
|
@@ -109,7 +109,8 @@ const validators = {
|
|
|
109
109
|
VIRTUAL_USERS_COUNT: num(),
|
|
110
110
|
VIRTUAL_USERS_GENERATE_DURATION: num(),
|
|
111
111
|
WALLET_SYNC_TIMEOUT_IN_MS: num({ default: undefined }),
|
|
112
|
-
WORKER_PARALLEL_TRANSACTION: num()
|
|
112
|
+
WORKER_PARALLEL_TRANSACTION: num(),
|
|
113
|
+
WS_PROVIDER_URL: str()
|
|
113
114
|
} as const;
|
|
114
115
|
|
|
115
116
|
type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][];
|
|
@@ -159,6 +160,7 @@ export const walletVariables = [
|
|
|
159
160
|
'TX_SUBMIT_PROVIDER_PARAMS',
|
|
160
161
|
'UTXO_PROVIDER',
|
|
161
162
|
'UTXO_PROVIDER_PARAMS',
|
|
163
|
+
'WS_PROVIDER_URL',
|
|
162
164
|
'ADDRESS_DISCOVERY',
|
|
163
165
|
'NETWORK_SPEED'
|
|
164
166
|
] as const;
|
package/src/factories.ts
CHANGED
|
@@ -34,12 +34,8 @@ import {
|
|
|
34
34
|
Witnesser,
|
|
35
35
|
util
|
|
36
36
|
} from '@cardano-sdk/key-management';
|
|
37
|
-
import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger';
|
|
38
|
-
import { Logger } from 'ts-log';
|
|
39
|
-
import { NodeTxSubmitProvider } from '@cardano-sdk/cardano-services';
|
|
40
|
-
import { OgmiosObservableCardanoNode } from '@cardano-sdk/ogmios';
|
|
41
|
-
import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor';
|
|
42
37
|
import {
|
|
38
|
+
CardanoWsClient,
|
|
43
39
|
assetInfoHttpProvider,
|
|
44
40
|
chainHistoryHttpProvider,
|
|
45
41
|
handleHttpProvider,
|
|
@@ -49,8 +45,14 @@ import {
|
|
|
49
45
|
txSubmitHttpProvider,
|
|
50
46
|
utxoHttpProvider
|
|
51
47
|
} from '@cardano-sdk/cardano-services-client';
|
|
48
|
+
import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger';
|
|
49
|
+
import { Logger } from 'ts-log';
|
|
50
|
+
import { NodeTxSubmitProvider } from '@cardano-sdk/cardano-services';
|
|
51
|
+
import { OgmiosObservableCardanoNode } from '@cardano-sdk/ogmios';
|
|
52
|
+
import { TrezorKeyAgent } from '@cardano-sdk/hardware-trezor';
|
|
52
53
|
import { createStubStakePoolProvider } from '@cardano-sdk/util-dev';
|
|
53
54
|
import { filter, firstValueFrom, of } from 'rxjs';
|
|
55
|
+
import { getEnv, walletVariables } from './environment';
|
|
54
56
|
import DeviceConnection from '@cardano-foundation/ledgerjs-hw-app-cardano';
|
|
55
57
|
import memoize from 'lodash/memoize.js';
|
|
56
58
|
|
|
@@ -63,6 +65,7 @@ const HTTP_PROVIDER = 'http';
|
|
|
63
65
|
const OGMIOS_PROVIDER = 'ogmios';
|
|
64
66
|
const STUB_PROVIDER = 'stub';
|
|
65
67
|
const MISSING_URL_PARAM = 'Missing URL';
|
|
68
|
+
const WS_PROVIDER = 'ws';
|
|
66
69
|
|
|
67
70
|
export type CreateKeyAgent = (dependencies: KeyAgentDependencies) => Promise<AsyncKeyAgent>;
|
|
68
71
|
export const keyManagementFactory = new ProviderFactory<CreateKeyAgent>();
|
|
@@ -128,6 +131,16 @@ networkInfoProviderFactory.register(
|
|
|
128
131
|
}
|
|
129
132
|
);
|
|
130
133
|
|
|
134
|
+
networkInfoProviderFactory.register(WS_PROVIDER, (_params: any, logger: Logger): Promise<NetworkInfoProvider> => {
|
|
135
|
+
const env = getEnv(walletVariables);
|
|
136
|
+
|
|
137
|
+
if (env.WS_PROVIDER_URL === undefined) throw new Error(`${networkInfoHttpProvider.name}: ${MISSING_URL_PARAM}`);
|
|
138
|
+
|
|
139
|
+
const wsClient = new CardanoWsClient({ logger }, { url: new URL(env.WS_PROVIDER_URL) });
|
|
140
|
+
|
|
141
|
+
return Promise.resolve(wsClient.networkInfoProvider);
|
|
142
|
+
});
|
|
143
|
+
|
|
131
144
|
rewardsProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logger): Promise<RewardsProvider> => {
|
|
132
145
|
if (params.baseUrl === undefined) throw new Error(`${rewardsHttpProvider.name}: ${MISSING_URL_PARAM}`);
|
|
133
146
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// cSpell:ignore loadimpact
|
|
2
|
+
|
|
3
|
+
import { Counter, Trend } from 'k6/metrics';
|
|
4
|
+
import { check } from 'k6';
|
|
5
|
+
import ws from 'k6/ws';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line no-undef
|
|
8
|
+
const { TARGET_ENV, TARGET_NET, URL, WALLETS } = Object.assign({ WALLETS: '10000' }, __ENV);
|
|
9
|
+
const url = TARGET_ENV ? `wss://${TARGET_ENV}-${TARGET_NET}${TARGET_ENV === 'ops' ? '-1' : ''}.lw.iog.io/ws` : URL;
|
|
10
|
+
|
|
11
|
+
if (TARGET_ENV && !['dev', 'ops', 'staging', 'prod'].includes(TARGET_ENV))
|
|
12
|
+
throw new Error(`Not valid TARGET_ENV: ${TARGET_ENV}`);
|
|
13
|
+
if (TARGET_NET && !['preview', 'preprod', 'sanchonet', 'mainnet'].includes(TARGET_NET))
|
|
14
|
+
throw new Error(`Not valid TARGET_NET: ${TARGET_NET}`);
|
|
15
|
+
if (!(TARGET_ENV && TARGET_NET) && !URL) throw new Error('Please specify both TARGET_ENV and TARGET_NET or URL');
|
|
16
|
+
|
|
17
|
+
export const options = {
|
|
18
|
+
ext: {
|
|
19
|
+
loadimpact: {
|
|
20
|
+
apm: [],
|
|
21
|
+
distribution: { 'amazon:us:portland': { loadZone: 'amazon:us:portland', percent: 100 } }
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
scenarios: {
|
|
25
|
+
connections: {
|
|
26
|
+
executor: 'ramping-vus',
|
|
27
|
+
gracefulRampDown: '0s',
|
|
28
|
+
gracefulStop: '120s',
|
|
29
|
+
stages: [{ duration: '3s', target: Number.parseInt(WALLETS, 10) }],
|
|
30
|
+
startVUs: 10
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const operationalTrend = new Trend('_operational', true);
|
|
36
|
+
const unexpectedCloseCounter = new Counter('_unexpected_close');
|
|
37
|
+
|
|
38
|
+
export const run = () => {
|
|
39
|
+
const begin = Date.now();
|
|
40
|
+
|
|
41
|
+
const res = ws.connect(url, null, (socket) => {
|
|
42
|
+
let closed = false;
|
|
43
|
+
let firstMessage = true;
|
|
44
|
+
|
|
45
|
+
socket.on('message', () => {
|
|
46
|
+
if (firstMessage) {
|
|
47
|
+
operationalTrend.add(Date.now() - begin);
|
|
48
|
+
firstMessage = false;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Count unexpected close
|
|
53
|
+
socket.on('close', () => {
|
|
54
|
+
if (!closed) unexpectedCloseCounter.add(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Heartbeat
|
|
58
|
+
socket.setTimeout(() => socket.send('{}'), 30 * 1000);
|
|
59
|
+
|
|
60
|
+
// End the test after 80"
|
|
61
|
+
socket.setTimeout(() => {
|
|
62
|
+
closed = true;
|
|
63
|
+
socket.close();
|
|
64
|
+
}, 80 * 1000);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
check(res, { 'status is 101': (r) => r && r.status === 101 });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default run;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CardanoWsClient } from '@cardano-sdk/cardano-services-client';
|
|
2
|
+
import { getEnv, walletVariables } from '../../src';
|
|
3
|
+
import { logger } from '@cardano-sdk/util-dev';
|
|
4
|
+
|
|
5
|
+
const env = getEnv([...walletVariables]);
|
|
6
|
+
|
|
7
|
+
describe('Web Socket', () => {
|
|
8
|
+
let client: CardanoWsClient;
|
|
9
|
+
|
|
10
|
+
const openClient = () => (client = new CardanoWsClient({ logger }, { url: new URL(env.WS_PROVIDER_URL) }));
|
|
11
|
+
|
|
12
|
+
const closeClient = () => (client ? client.close() : Promise.resolve());
|
|
13
|
+
|
|
14
|
+
afterEach(closeClient);
|
|
15
|
+
|
|
16
|
+
it('CardanoWsClient.epoch$ emits on epoch rollover', (done) => {
|
|
17
|
+
openClient();
|
|
18
|
+
|
|
19
|
+
// If it emits on epoch rollover ok, otherwise the test fails on timeout
|
|
20
|
+
const subscription = client.epoch$.subscribe(() => {
|
|
21
|
+
subscription.unsubscribe();
|
|
22
|
+
done();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { CardanoWsClient } from '@cardano-sdk/cardano-services-client';
|
|
2
|
+
import {
|
|
3
|
+
CardanoWsServer,
|
|
4
|
+
GenesisData,
|
|
5
|
+
createDnsResolver,
|
|
6
|
+
getOgmiosCardanoNode,
|
|
7
|
+
util
|
|
8
|
+
} from '@cardano-sdk/cardano-services';
|
|
9
|
+
import { HealthCheckResponse, WsProvider } from '@cardano-sdk/core';
|
|
10
|
+
import { OgmiosCardanoNode } from '@cardano-sdk/ogmios';
|
|
11
|
+
import { Pool } from 'pg';
|
|
12
|
+
import { filter, firstValueFrom } from 'rxjs';
|
|
13
|
+
import { getEnv, walletVariables } from '../../src';
|
|
14
|
+
import { getPort } from 'get-port-please';
|
|
15
|
+
import { logger } from '@cardano-sdk/util-dev';
|
|
16
|
+
|
|
17
|
+
const env = getEnv([...walletVariables, 'DB_SYNC_CONNECTION_STRING', 'OGMIOS_URL']);
|
|
18
|
+
|
|
19
|
+
const wsProviderReady = (provider: WsProvider) =>
|
|
20
|
+
new Promise<void>((resolve, reject) => {
|
|
21
|
+
// eslint-disable-next-line prefer-const
|
|
22
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
23
|
+
|
|
24
|
+
const subscription = provider.health$.subscribe(({ ok }) => {
|
|
25
|
+
if (ok) {
|
|
26
|
+
if (timeout) clearTimeout(timeout);
|
|
27
|
+
subscription.unsubscribe();
|
|
28
|
+
resolve();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
timeout = setTimeout(() => {
|
|
33
|
+
subscription.unsubscribe();
|
|
34
|
+
reject(new Error('WsProvider timeout'));
|
|
35
|
+
}, 10_000);
|
|
36
|
+
|
|
37
|
+
timeout.unref();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const wsProviderReadyAgain = (provider: WsProvider, close: () => Promise<unknown>) =>
|
|
41
|
+
new Promise<void>(async (resolve, reject) => {
|
|
42
|
+
const oks: boolean[] = [];
|
|
43
|
+
let closed = false;
|
|
44
|
+
// eslint-disable-next-line prefer-const
|
|
45
|
+
let timeout: NodeJS.Timeout | undefined;
|
|
46
|
+
|
|
47
|
+
const subscription = provider.health$.subscribe(({ ok }) => {
|
|
48
|
+
oks.push(ok);
|
|
49
|
+
|
|
50
|
+
if (closed && ok) {
|
|
51
|
+
if (timeout) clearTimeout(timeout);
|
|
52
|
+
subscription.unsubscribe();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const [, ...last] = oks;
|
|
56
|
+
|
|
57
|
+
// The first emitted event is the ok buffered one
|
|
58
|
+
// next we expect at least one not ok event i.e. the close function had the desired effect
|
|
59
|
+
// last we expect one more ok event when provider is operational once again
|
|
60
|
+
expect(oks.length).toBeGreaterThanOrEqual(3);
|
|
61
|
+
expect(oks[0]).toBeTruthy();
|
|
62
|
+
expect(oks[1]).toBeFalsy();
|
|
63
|
+
expect(last.some((result) => result));
|
|
64
|
+
|
|
65
|
+
resolve();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
reject(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await close();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
reject(error);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
closed = true;
|
|
79
|
+
|
|
80
|
+
timeout = setTimeout(() => {
|
|
81
|
+
subscription.unsubscribe();
|
|
82
|
+
reject(new Error('WsProvider timeout'));
|
|
83
|
+
}, 10_000);
|
|
84
|
+
|
|
85
|
+
timeout.unref();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Web Socket', () => {
|
|
89
|
+
let db: Pool;
|
|
90
|
+
let cardanoNode: OgmiosCardanoNode;
|
|
91
|
+
let genesisData: GenesisData;
|
|
92
|
+
let port: number;
|
|
93
|
+
|
|
94
|
+
let client: CardanoWsClient;
|
|
95
|
+
let server: CardanoWsServer;
|
|
96
|
+
|
|
97
|
+
const openClient = (heartbeatInterval = 55) =>
|
|
98
|
+
(client = new CardanoWsClient({ logger }, { heartbeatInterval, url: new URL(`ws://localhost:${port}/ws`) }));
|
|
99
|
+
|
|
100
|
+
const openServer = (heartbeatTimeout = 60) =>
|
|
101
|
+
(server = new CardanoWsServer(
|
|
102
|
+
{ cardanoNode, db, genesisData, logger },
|
|
103
|
+
{ dbCacheTtl: 120, heartbeatTimeout, port }
|
|
104
|
+
));
|
|
105
|
+
|
|
106
|
+
const closeClient = () => (client ? client.close() : Promise.resolve());
|
|
107
|
+
const closeServer = () => (server ? new Promise<void>((resolve) => server.close(resolve)) : Promise.resolve());
|
|
108
|
+
|
|
109
|
+
const listenToClientHealthFor15Seconds = async () => {
|
|
110
|
+
const health: HealthCheckResponse[] = [];
|
|
111
|
+
const subscription = client.health$.subscribe((value) => health.push(value));
|
|
112
|
+
|
|
113
|
+
// Listen on client.health$ for 15"
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, 15_000));
|
|
115
|
+
|
|
116
|
+
subscription.unsubscribe();
|
|
117
|
+
|
|
118
|
+
return health;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
beforeAll(async () => {
|
|
122
|
+
const dnsResolver = createDnsResolver({ factor: 1.1, maxRetryTime: 1000 }, logger);
|
|
123
|
+
|
|
124
|
+
cardanoNode = await getOgmiosCardanoNode(dnsResolver, logger, { ogmiosUrl: new URL(env.OGMIOS_URL) });
|
|
125
|
+
db = new Pool({ connectionString: env.DB_SYNC_CONNECTION_STRING });
|
|
126
|
+
genesisData = await util.loadGenesisData('local-network/config/network/cardano-node/config.json');
|
|
127
|
+
port = await getPort();
|
|
128
|
+
|
|
129
|
+
await cardanoNode.initialize();
|
|
130
|
+
await cardanoNode.start();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
afterAll(() => Promise.all([cardanoNode.shutdown(), db.end()]));
|
|
134
|
+
|
|
135
|
+
afterEach(() => Promise.all([closeClient(), closeServer()]));
|
|
136
|
+
|
|
137
|
+
it('Server can re-connect to DB if NOTIFY connection drops', async () => {
|
|
138
|
+
// Close server db connection from DB server side
|
|
139
|
+
const query =
|
|
140
|
+
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND query = 'LISTEN sdk_tip'";
|
|
141
|
+
|
|
142
|
+
openServer();
|
|
143
|
+
await wsProviderReady(server);
|
|
144
|
+
|
|
145
|
+
await wsProviderReadyAgain(server, () => db.query(query));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('Client can re-connect to server if web socket connection drops', async () => {
|
|
149
|
+
openServer();
|
|
150
|
+
await wsProviderReady(server);
|
|
151
|
+
|
|
152
|
+
openClient();
|
|
153
|
+
await wsProviderReady(client);
|
|
154
|
+
|
|
155
|
+
await wsProviderReadyAgain(client, async () => {
|
|
156
|
+
// Close the server and open a new one
|
|
157
|
+
await closeServer();
|
|
158
|
+
openServer();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('Server disconnects clients on heartbeat timeout', async () => {
|
|
163
|
+
// Open a server with 3" heartbeat timeout
|
|
164
|
+
openServer(3);
|
|
165
|
+
await wsProviderReady(server);
|
|
166
|
+
|
|
167
|
+
openClient();
|
|
168
|
+
await wsProviderReady(client);
|
|
169
|
+
|
|
170
|
+
const health = await listenToClientHealthFor15Seconds();
|
|
171
|
+
|
|
172
|
+
// Considering the server performs timeouts check every 10"
|
|
173
|
+
// We expect the heath state of the client goes up and down more time
|
|
174
|
+
expect(health.length).toBeGreaterThanOrEqual(3);
|
|
175
|
+
// We expect the heath state of the client goes up at least twice
|
|
176
|
+
expect(health.filter(({ ok }) => ok).length).toBeGreaterThanOrEqual(2);
|
|
177
|
+
// We expect the heath state of the client goes down at least once
|
|
178
|
+
expect(health.some(({ ok }) => !ok)).toBeTruthy();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("Server doesn't disconnects clients without heartbeat timeouts", async () => {
|
|
182
|
+
// Open a server with 3" heartbeat timeout
|
|
183
|
+
openServer(3);
|
|
184
|
+
await wsProviderReady(server);
|
|
185
|
+
|
|
186
|
+
// Open a client with 2" heartbeat interval
|
|
187
|
+
openClient(2);
|
|
188
|
+
await wsProviderReady(client);
|
|
189
|
+
|
|
190
|
+
const health = await listenToClientHealthFor15Seconds();
|
|
191
|
+
|
|
192
|
+
// We expect only the buffered ok heath state
|
|
193
|
+
expect(health.length).toBe(1);
|
|
194
|
+
expect(health[0].ok).toBeTruthy();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('CardanoWsClient.networkInfoProvider', () => {
|
|
198
|
+
it('It throws when disconnected but when starting', async () => {
|
|
199
|
+
openServer();
|
|
200
|
+
await wsProviderReady(server);
|
|
201
|
+
|
|
202
|
+
openClient();
|
|
203
|
+
|
|
204
|
+
// This test doesn't calls wsProviderReady(client)
|
|
205
|
+
// to check the provider doesn't throw if called before its init sequence completed
|
|
206
|
+
await expect(client.networkInfoProvider.ledgerTip()).resolves.toHaveProperty('blockNo');
|
|
207
|
+
|
|
208
|
+
await closeServer();
|
|
209
|
+
await firstValueFrom(client.health$.pipe(filter(({ ok }) => !ok)));
|
|
210
|
+
|
|
211
|
+
await expect(client.networkInfoProvider.ledgerTip()).rejects.toThrowError('CONNECTION_FAILURE');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('If called when still starting, it throws on connect error', async () => {
|
|
215
|
+
openClient();
|
|
216
|
+
|
|
217
|
+
await expect(client.networkInfoProvider.ledgerTip()).rejects.toThrowError('CONNECTION_FAILURE');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|