@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.
@@ -111,6 +111,11 @@ services:
111
111
  METADATA_JOB_RETRY_DELAY: 60
112
112
  POOLS_METRICS_INTERVAL: 50
113
113
 
114
+ ws-server:
115
+ volumes:
116
+ - ./local-network/config/network:/config
117
+ - sdk-ipc:/sdk-ipc
118
+
114
119
  volumes:
115
120
  sdk-ipc:
116
121
  driver: local
package/jest.config.js CHANGED
@@ -34,6 +34,7 @@ module.exports = {
34
34
  project('providers'),
35
35
  project('wallet_epoch_0'),
36
36
  project('wallet_epoch_3'),
37
+ project('ws-server'),
37
38
  {
38
39
  ...commonProjectProps,
39
40
  displayName: 'wallet-real-ada',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardano-sdk/e2e",
3
- "version": "0.36.7",
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.12",
83
- "@cardano-sdk/cardano-services-client": "~0.19.12",
84
- "@cardano-sdk/core": "~0.35.2",
85
- "@cardano-sdk/crypto": "~0.1.26",
86
- "@cardano-sdk/hardware-ledger": "~0.9.13",
87
- "@cardano-sdk/hardware-trezor": "~0.4.33",
88
- "@cardano-sdk/input-selection": "~0.13.6",
89
- "@cardano-sdk/key-management": "~0.20.11",
90
- "@cardano-sdk/ogmios": "~0.15.33",
91
- "@cardano-sdk/tx-construction": "~0.19.6",
92
- "@cardano-sdk/util": "~0.15.3",
93
- "@cardano-sdk/util-dev": "~0.21.6",
94
- "@cardano-sdk/util-rxjs": "~0.7.19",
95
- "@cardano-sdk/wallet": "~0.38.7",
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.24",
126
- "@cardano-sdk/projection": "~0.11.23",
127
- "@cardano-sdk/projection-typeorm": "~0.8.25",
128
- "@cardano-sdk/web-extension": "~0.29.7",
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": "271f2e3658294b247f15467ae863d66784a41228"
184
+ "gitHead": "8251245efc792b61a6a5c2be4b42fcbaeaf2312a"
183
185
  }
@@ -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
+ });