@milaboratories/pl-middle-layer 1.37.3 → 1.37.5

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.
Files changed (38) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +41 -15
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1751 -1472
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/mutator/context_export.d.ts +2 -2
  8. package/dist/mutator/context_export.d.ts.map +1 -1
  9. package/dist/mutator/template/render_template.d.ts +2 -2
  10. package/dist/mutator/template/render_template.d.ts.map +1 -1
  11. package/dist/network_check/network_check.d.ts +40 -0
  12. package/dist/network_check/network_check.d.ts.map +1 -0
  13. package/dist/network_check/network_check.test.d.ts.map +1 -0
  14. package/dist/network_check/pings.d.ts +32 -0
  15. package/dist/network_check/pings.d.ts.map +1 -0
  16. package/dist/network_check/template.d.ts +33 -0
  17. package/dist/network_check/template.d.ts.map +1 -0
  18. package/dist/network_check/template.test.d.ts +2 -0
  19. package/dist/network_check/template.test.d.ts.map +1 -0
  20. package/dist/network_check/test_utils.d.ts +6 -0
  21. package/dist/network_check/test_utils.d.ts.map +1 -0
  22. package/dist/pool/driver.d.ts +10 -1
  23. package/dist/pool/driver.d.ts.map +1 -1
  24. package/package.json +9 -9
  25. package/src/index.ts +1 -1
  26. package/src/mutator/template/render_template.ts +2 -2
  27. package/src/{network_check.test.ts → network_check/network_check.test.ts} +3 -3
  28. package/src/network_check/network_check.ts +369 -0
  29. package/src/network_check/pings.ts +154 -0
  30. package/src/network_check/template.test.ts +83 -0
  31. package/src/network_check/template.ts +330 -0
  32. package/src/network_check/test_utils.ts +9 -0
  33. package/src/pool/driver.ts +21 -5
  34. package/dist/network_check.d.ts +0 -29
  35. package/dist/network_check.d.ts.map +0 -1
  36. package/dist/network_check.test.d.ts.map +0 -1
  37. package/src/network_check.ts +0 -329
  38. /package/dist/{network_check.test.d.ts → network_check/network_check.test.d.ts} +0 -0
@@ -0,0 +1,369 @@
1
+ /** A utility to check network problems and gather statistics.
2
+ * It's useful when we cannot connect to the server of a company
3
+ * because of security reasons,
4
+ * but they can send us and their DevOps team this report.
5
+ *
6
+ * What we check:
7
+ * - pings to backend
8
+ * - block registry for block overview and ui.
9
+ * - autoupdate CDN.
10
+ * - upload workflow to backend (workflow part via our API).
11
+ * - the desktop could do multipart upload.
12
+ * - the desktop could download files from S3.
13
+ * - backend could download software and run it.
14
+ * - backend could run python software.
15
+ * TODO:
16
+ * - try to get something from backend's library storage.
17
+ *
18
+ * We don't check backend access to S3 storage, it is checked on the start of backend.
19
+ */
20
+
21
+ import type { AuthInformation, PlClientConfig } from '@milaboratories/pl-client';
22
+ import { PlClient, UnauthenticatedPlClient, plAddressToConfig } from '@milaboratories/pl-client';
23
+ import type { MiLogger } from '@milaboratories/ts-helpers';
24
+ import { ConsoleLoggerAdapter, HmacSha256Signer } from '@milaboratories/ts-helpers';
25
+ import { channel } from 'node:diagnostics_channel';
26
+ import type { ClientDownload, ClientUpload } from '@milaboratories/pl-drivers';
27
+ import { LsDriver, createDownloadClient, createUploadBlobClient } from '@milaboratories/pl-drivers';
28
+ import type { HttpNetworkReport, NetworkReport } from './pings';
29
+ import { autoUpdateCdnPings, backendPings, blockGARegistryOverviewPings, blockGARegistryUiPings, blockRegistryOverviewPings, blockRegistryUiPings, reportToString } from './pings';
30
+ import type { Dispatcher } from 'undici';
31
+ import type { TemplateReport } from './template';
32
+ import { uploadTemplate, uploadFile, downloadFile, createTempFile, pythonSoftware, softwareCheck, createBigTempFile } from './template';
33
+
34
+ /** All reports we need to collect. */
35
+ interface NetworkReports {
36
+ plPings: NetworkReport<string>[];
37
+
38
+ blockRegistryOverviewChecks: HttpNetworkReport[];
39
+ blockGARegistryOverviewChecks: HttpNetworkReport[];
40
+ blockRegistryUiChecks: HttpNetworkReport[];
41
+ blockGARegistryUiChecks: HttpNetworkReport[];
42
+
43
+ autoUpdateCdnChecks: HttpNetworkReport[];
44
+
45
+ uploadTemplateCheck: TemplateReport;
46
+ uploadFileCheck: TemplateReport;
47
+ downloadFileCheck: TemplateReport;
48
+ softwareCheck: TemplateReport;
49
+ pythonSoftwareCheck: TemplateReport;
50
+ }
51
+
52
+ export interface CheckNetworkOpts {
53
+ /** Platforma Backend pings options. */
54
+ pingCheckDurationMs: number;
55
+ pingTimeoutMs: number;
56
+ maxPingsPerSecond: number;
57
+
58
+ /** An options for CDN and block registry. */
59
+ httpTimeoutMs: number;
60
+
61
+ /** Block registry pings options. */
62
+ blockRegistryDurationMs: number;
63
+ maxRegistryChecksPerSecond: number;
64
+ blockRegistryUrl: string;
65
+ blockGARegistryUrl: string;
66
+ blockOverviewPath: string;
67
+ blockUiPath: string;
68
+
69
+ /** CDN for auto-update pings options. */
70
+ autoUpdateCdnDurationMs: number;
71
+ maxAutoUpdateCdnChecksPerSecond: number;
72
+ autoUpdateCdnUrl: string;
73
+
74
+ bodyLimit: number;
75
+ }
76
+
77
+ /** Checks connectivity to Platforma Backend, to block registry
78
+ * and to auto-update CDN,
79
+ * and generates a string report. */
80
+ export async function checkNetwork(
81
+ plCredentials: string,
82
+ plUser: string | undefined,
83
+ plPassword: string | undefined,
84
+ optsOverrides: Partial<CheckNetworkOpts> = {},
85
+ ): Promise<string> {
86
+ const undiciLogs: any[] = [];
87
+ // Subscribe to all Undici diagnostic events
88
+ undiciEvents.forEach((event) => {
89
+ const diagnosticChannel = channel(event);
90
+ diagnosticChannel.subscribe((message: any) => {
91
+ const timestamp = new Date().toISOString();
92
+ const data = { ...message };
93
+ if (data?.response?.headers) {
94
+ data.response = { ...data.response };
95
+ data.response.headers = data.response.headers.slice();
96
+ data.response.headers = data.response.headers.map((h: any) => h.toString());
97
+ }
98
+
99
+ // we try to upload big files, don't include the buffer in the report.
100
+ if (data?.request?.body) {
101
+ data.request = { ...data.request };
102
+ data.request.body = `too big`;
103
+ }
104
+
105
+ undiciLogs.push(
106
+ JSON.stringify({
107
+ timestamp,
108
+ event,
109
+ data,
110
+ }),
111
+ );
112
+ });
113
+ });
114
+
115
+ const {
116
+ logger,
117
+ plConfig,
118
+ client,
119
+ downloadClient,
120
+ uploadBlobClient,
121
+ lsDriver,
122
+ httpClient,
123
+ ops,
124
+ } = await initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides);
125
+
126
+ const { filePath: filePathToDownload, fileContent: fileContentToDownload } = await createTempFile();
127
+ const { filePath: filePathToUpload } = await createBigTempFile();
128
+
129
+ const report: NetworkReports = {
130
+ plPings: await backendPings(ops, plConfig),
131
+ blockRegistryOverviewChecks: await blockRegistryOverviewPings(ops, httpClient),
132
+ blockGARegistryOverviewChecks: await blockGARegistryOverviewPings(ops, httpClient),
133
+ blockRegistryUiChecks: await blockRegistryUiPings(ops, httpClient),
134
+ blockGARegistryUiChecks: await blockGARegistryUiPings(ops, httpClient),
135
+
136
+ autoUpdateCdnChecks: await autoUpdateCdnPings(ops, httpClient),
137
+
138
+ uploadTemplateCheck: await uploadTemplate(logger, client, 'Jack'),
139
+ uploadFileCheck: await uploadFile(logger, lsDriver, uploadBlobClient, client, filePathToUpload),
140
+ downloadFileCheck: await downloadFile(logger, client, lsDriver, uploadBlobClient, downloadClient, filePathToDownload, fileContentToDownload),
141
+ softwareCheck: await softwareCheck(client),
142
+ pythonSoftwareCheck: await pythonSoftware(client, 'Jack'),
143
+ };
144
+
145
+ return reportsToString(report, plCredentials, ops, undiciLogs);
146
+ }
147
+
148
+ export async function initNetworkCheck(
149
+ plCredentials: string,
150
+ plUser: string | undefined,
151
+ plPassword: string | undefined,
152
+ optsOverrides: Partial<CheckNetworkOpts> = {},
153
+ ): Promise<{
154
+ logger: MiLogger;
155
+ plConfig: PlClientConfig;
156
+ client: PlClient;
157
+ downloadClient: ClientDownload;
158
+ uploadBlobClient: ClientUpload;
159
+ lsDriver: LsDriver;
160
+ httpClient: Dispatcher;
161
+ ops: CheckNetworkOpts;
162
+ terminate: () => Promise<void>;
163
+ }> {
164
+ const ops: CheckNetworkOpts = {
165
+ pingCheckDurationMs: 10000,
166
+ pingTimeoutMs: 3000,
167
+ maxPingsPerSecond: 50,
168
+
169
+ httpTimeoutMs: 3000,
170
+
171
+ blockRegistryDurationMs: 3000,
172
+ maxRegistryChecksPerSecond: 1,
173
+
174
+ blockRegistryUrl: 'https://blocks.pl-open.science',
175
+ blockGARegistryUrl: 'https://blocks-ga.pl-open.science',
176
+ blockOverviewPath: 'v2/overview.json',
177
+ blockUiPath: 'v2/milaboratories/samples-and-data/1.7.0/ui.tgz',
178
+
179
+ autoUpdateCdnDurationMs: 5000,
180
+ maxAutoUpdateCdnChecksPerSecond: 1,
181
+ autoUpdateCdnUrl:
182
+ 'https://cdn.platforma.bio/software/platforma-desktop-v2/windows/amd64/latest.yml',
183
+
184
+ bodyLimit: 300,
185
+
186
+ ...optsOverrides,
187
+ };
188
+
189
+ const plConfig = plAddressToConfig(plCredentials, {
190
+ defaultRequestTimeout: ops.pingTimeoutMs,
191
+ });
192
+
193
+ // exposing alternative root for fields not to interfere with
194
+ // projects of the user.
195
+ plConfig.alternativeRoot = `check_network_${Date.now()}`;
196
+
197
+ const uaClient = new UnauthenticatedPlClient(plConfig);
198
+
199
+ let auth: AuthInformation = {};
200
+ if (plUser && plPassword) {
201
+ auth = await uaClient.login(plUser, plPassword);
202
+ }
203
+
204
+ const client = await PlClient.init(plCredentials, { authInformation: auth });
205
+
206
+ const httpClient = uaClient.ll.httpDispatcher;
207
+ const logger = new ConsoleLoggerAdapter();
208
+
209
+ // FIXME: do we need to get an actual secret?
210
+ const signer = new HmacSha256Signer('localSecret');
211
+
212
+ // We could initialize middle-layer here, but for now it seems like an overkill.
213
+ // Here's the code to do it:
214
+ //
215
+ // const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'platforma-network-check-'));
216
+ // const ml = await MiddleLayer.init(client, tmpDir, {
217
+ // logger,
218
+ // localSecret: '',
219
+ // localProjections: [],
220
+ // openFileDialogCallback: () => Promise.resolve([]),
221
+ // preferredUpdateChannel: 'stable',
222
+ // });
223
+
224
+ const downloadClient = createDownloadClient(logger, client, []);
225
+ const uploadBlobClient = createUploadBlobClient(client, logger);
226
+
227
+ const lsDriver = await LsDriver.init(
228
+ logger,
229
+ client,
230
+ signer,
231
+ [],
232
+ () => Promise.resolve([]),
233
+ [],
234
+ );
235
+
236
+ const terminate = async () => {
237
+ downloadClient.close();
238
+ uploadBlobClient.close();
239
+ await httpClient.close();
240
+ await client.close();
241
+ };
242
+
243
+ return {
244
+ logger,
245
+ plConfig,
246
+ client,
247
+ downloadClient,
248
+ uploadBlobClient,
249
+ lsDriver,
250
+ httpClient,
251
+ ops,
252
+ terminate,
253
+ };
254
+ }
255
+
256
+ function reportsToString(
257
+ report: NetworkReports,
258
+ plEndpoint: string,
259
+ opts: CheckNetworkOpts,
260
+ undiciLogs: any[],
261
+ ): string {
262
+ const successPings = report.plPings.filter((p) => p.response.ok);
263
+ const failedPings = report.plPings.filter((p) => !p.response.ok);
264
+ const successPingsBodies = [
265
+ ...new Set(successPings.map((p) => JSON.stringify((p.response as any).value))),
266
+ ];
267
+
268
+ const pings = reportToString(report.plPings);
269
+ const blockRegistryOverview = reportToString(report.blockRegistryOverviewChecks);
270
+ const blockGARegistryOverview = reportToString(report.blockGARegistryOverviewChecks);
271
+ const blockRegistryUi = reportToString(report.blockRegistryUiChecks);
272
+ const blockGARegistryUi = reportToString(report.blockGARegistryUiChecks);
273
+ const autoUpdateCdn = reportToString(report.autoUpdateCdnChecks);
274
+
275
+ const summary = (ok: boolean) => ok ? 'OK' : 'FAILED';
276
+
277
+ return `
278
+ Network report:
279
+ pl endpoint: ${plEndpoint};
280
+
281
+ summary:
282
+ ${summary(pings.ok)} pings to Platforma Backend
283
+ ${summary(blockRegistryOverview.ok)} block registry overview
284
+ ${summary(blockGARegistryOverview.ok)} block ga registry overview
285
+ ${summary(blockRegistryUi.ok)} block registry ui
286
+ ${summary(blockGARegistryUi.ok)} block ga registry ui
287
+ ${summary(autoUpdateCdn.ok)} auto-update CDN
288
+ ${summary(report.uploadTemplateCheck.ok)} upload template
289
+ ${summary(report.uploadFileCheck.ok)} upload file
290
+ ${summary(report.downloadFileCheck.ok)} download file
291
+ ${summary(report.softwareCheck.ok)} software check
292
+ ${summary(report.pythonSoftwareCheck.ok)} python software check
293
+
294
+ details:
295
+ options: ${JSON.stringify(opts, null, 2)}.
296
+
297
+ Upload template response: ${report.uploadTemplateCheck.message}
298
+
299
+ Upload file response: ${report.uploadFileCheck.message}
300
+
301
+ Download file response: ${report.downloadFileCheck.message}
302
+
303
+ Software check response: ${report.softwareCheck.message}
304
+ Python software check response: ${report.pythonSoftwareCheck.message}
305
+ Platforma pings: ${pings.details}
306
+
307
+ Block registry overview responses: ${blockRegistryOverview.details}
308
+
309
+ Block ga registry overview responses: ${blockGARegistryOverview.details}
310
+
311
+ Block registry ui responses: ${blockRegistryUi.details}
312
+
313
+ Block ga registry ui responses: ${blockGARegistryUi.details}
314
+
315
+ Auto-update CDN responses: ${autoUpdateCdn.details}
316
+
317
+ dumps:
318
+ Block registry overview dumps:
319
+ ${JSON.stringify(report.blockRegistryOverviewChecks, null, 2)}
320
+
321
+ Block ga registry overview dumps:
322
+ ${JSON.stringify(report.blockGARegistryOverviewChecks, null, 2)}
323
+
324
+ Block registry ui dumps:
325
+ ${JSON.stringify(report.blockRegistryUiChecks, null, 2)}
326
+
327
+ Block ga registry ui dumps:
328
+ ${JSON.stringify(report.blockGARegistryUiChecks, null, 2)}
329
+
330
+ Auto-update CDN dumps:
331
+ ${JSON.stringify(report.autoUpdateCdnChecks, null, 2)}
332
+
333
+ Platforma pings error dumps:
334
+ ${JSON.stringify(failedPings, null, 2)}
335
+
336
+ Platforma pings success dump examples:
337
+ ${JSON.stringify(successPingsBodies, null, 2)}
338
+
339
+ Undici logs:
340
+ ${undiciLogs.join('\n')}
341
+ `;
342
+ }
343
+
344
+ // List of Undici diagnostic channels
345
+ const undiciEvents: string[] = [
346
+ 'undici:request:create', // When a new request is created
347
+ 'undici:request:bodySent', // When the request body is sent
348
+ 'undici:request:headers', // When request headers are sent
349
+ 'undici:request:error', // When a request encounters an error
350
+ 'undici:request:trailers', // When a response completes.
351
+
352
+ 'undici:client:sendHeaders',
353
+ 'undici:client:beforeConnect',
354
+ 'undici:client:connected',
355
+ 'undici:client:connectError',
356
+
357
+ 'undici:socket:close', // When a socket is closed
358
+ 'undici:socket:connect', // When a socket connects
359
+ 'undici:socket:error', // When a socket encounters an error
360
+
361
+ 'undici:pool:request', // When a request is added to the pool
362
+ 'undici:pool:connect', // When a pool creates a new connection
363
+ 'undici:pool:disconnect', // When a pool connection is closed
364
+ 'undici:pool:destroy', // When a pool is destroyed
365
+ 'undici:dispatcher:request', // When a dispatcher processes a request
366
+ 'undici:dispatcher:connect', // When a dispatcher connects
367
+ 'undici:dispatcher:disconnect', // When a dispatcher disconnects
368
+ 'undici:dispatcher:retry', // When a dispatcher retries a request
369
+ ];
@@ -0,0 +1,154 @@
1
+ import type { ValueOrError } from '@milaboratories/ts-helpers';
2
+ import { setTimeout } from 'node:timers/promises';
3
+ import { request } from 'undici';
4
+ import type { Dispatcher } from 'undici';
5
+ import type { CheckNetworkOpts } from './network_check';
6
+ import { UnauthenticatedPlClient, type PlClientConfig } from '@milaboratories/pl-client';
7
+
8
+ /** A report about one concrete ping to the service. */
9
+ export interface NetworkReport<T> {
10
+ elapsedMs: number;
11
+ response: ValueOrError<T>;
12
+ }
13
+
14
+ export type HttpNetworkReport = NetworkReport<{
15
+ statusCode: number;
16
+ beginningOfBody: string;
17
+ }>;
18
+
19
+ export async function backendPings(ops: CheckNetworkOpts, plConfig: PlClientConfig): Promise<NetworkReport<string>[]> {
20
+ return await recordPings(ops.pingCheckDurationMs, ops.maxPingsPerSecond, async () => {
21
+ const uaClient = new UnauthenticatedPlClient(plConfig);
22
+ const response = await uaClient.ping();
23
+ return JSON.stringify(response).slice(0, ops.bodyLimit) + '...';
24
+ });
25
+ }
26
+
27
+ export async function blockRegistryOverviewPings(ops: CheckNetworkOpts, httpClient: Dispatcher): Promise<HttpNetworkReport[]> {
28
+ return await recordPings(
29
+ ops.blockRegistryDurationMs,
30
+ ops.maxRegistryChecksPerSecond,
31
+ async () =>
32
+ await requestUrl(new URL(ops.blockOverviewPath, ops.blockRegistryUrl), ops, httpClient),
33
+ );
34
+ }
35
+
36
+ export async function blockGARegistryOverviewPings(ops: CheckNetworkOpts, httpClient: Dispatcher): Promise<HttpNetworkReport[]> {
37
+ return await recordPings(
38
+ ops.blockRegistryDurationMs,
39
+ ops.maxRegistryChecksPerSecond,
40
+ async () => await requestUrl(new URL(ops.blockOverviewPath, ops.blockGARegistryUrl), ops, httpClient),
41
+ );
42
+ }
43
+
44
+ export async function blockRegistryUiPings(ops: CheckNetworkOpts, httpClient: Dispatcher): Promise<HttpNetworkReport[]> {
45
+ return await recordPings(
46
+ ops.blockRegistryDurationMs,
47
+ ops.maxRegistryChecksPerSecond,
48
+ async () => await requestUrl(new URL(ops.blockUiPath, ops.blockRegistryUrl), ops, httpClient),
49
+ );
50
+ }
51
+
52
+ export async function blockGARegistryUiPings(ops: CheckNetworkOpts, httpClient: Dispatcher): Promise<HttpNetworkReport[]> {
53
+ return await recordPings(
54
+ ops.blockRegistryDurationMs,
55
+ ops.maxRegistryChecksPerSecond,
56
+ async () => await requestUrl(new URL(ops.blockUiPath, ops.blockGARegistryUrl), ops, httpClient),
57
+ );
58
+ }
59
+
60
+ export async function autoUpdateCdnPings(ops: CheckNetworkOpts, httpClient: Dispatcher): Promise<HttpNetworkReport[]> {
61
+ return await recordPings(
62
+ ops.autoUpdateCdnDurationMs,
63
+ ops.maxAutoUpdateCdnChecksPerSecond,
64
+ async () => await requestUrl(ops.autoUpdateCdnUrl, ops, httpClient),
65
+ );
66
+ }
67
+
68
+ /** Executes a body several times per second up to the given duration,
69
+ * and returns results and elapsed time for every result. */
70
+ export async function recordPings<T>(
71
+ pingCheckDurationMs: number,
72
+ maxPingsPerSecond: number,
73
+ body: () => Promise<T>,
74
+ ): Promise<NetworkReport<T>[]> {
75
+ const startPings = Date.now();
76
+ const reports: NetworkReport<T>[] = [];
77
+
78
+ while (elapsed(startPings) < pingCheckDurationMs) {
79
+ const startPing = Date.now();
80
+ let response: ValueOrError<T>;
81
+ try {
82
+ response = { ok: true, value: await body() };
83
+ } catch (e) {
84
+ response = { ok: false, error: e };
85
+ }
86
+ const elapsedPing = elapsed(startPing);
87
+
88
+ reports.push({
89
+ elapsedMs: elapsedPing,
90
+ response,
91
+ });
92
+
93
+ const sleepBetweenPings = 1000 / maxPingsPerSecond - elapsedPing;
94
+
95
+ if (sleepBetweenPings > 0) {
96
+ await setTimeout(sleepBetweenPings);
97
+ }
98
+ }
99
+
100
+ return reports;
101
+ }
102
+
103
+ export async function requestUrl(url: string | URL, ops: CheckNetworkOpts, httpClient: Dispatcher) {
104
+ const { body: rawBody, statusCode } = await request(url, {
105
+ dispatcher: httpClient,
106
+ headersTimeout: ops.httpTimeoutMs,
107
+ bodyTimeout: ops.httpTimeoutMs,
108
+ });
109
+ const body = await rawBody.text();
110
+
111
+ return {
112
+ statusCode: statusCode,
113
+ beginningOfBody: body.slice(0, ops.bodyLimit) + '...',
114
+ };
115
+ }
116
+
117
+ export function elapsed(startMs: number): number {
118
+ return Date.now() - startMs;
119
+ }
120
+
121
+ export function reportToString<T>(report: NetworkReport<T>[]): {
122
+ ok: boolean;
123
+ details: string;
124
+ } {
125
+ const successes = report.filter((r) => r.response.ok);
126
+ const errorsLen = report.length - successes.length;
127
+ const { mean: mean, median: median } = elapsedStat(report);
128
+
129
+ const details = `
130
+ total: ${report.length};
131
+ successes: ${successes.length};
132
+ errors: ${errorsLen};
133
+ mean in ms: ${mean};
134
+ median in ms: ${median};
135
+ `;
136
+
137
+ return {
138
+ ok: errorsLen === 0,
139
+ details,
140
+ };
141
+ }
142
+
143
+ function elapsedStat(reports: { elapsedMs: number }[]) {
144
+ const checks = reports.map((p) => p.elapsedMs).sort();
145
+ const mean = checks.reduce((sum, p) => sum + p) / checks.length;
146
+
147
+ let median = undefined;
148
+ if (checks.length > 0) {
149
+ const mid = Math.floor(checks.length / 2);
150
+ median = checks.length % 2 ? checks[mid] : (checks[mid - 1] + checks[mid]) / 2;
151
+ }
152
+
153
+ return { mean, median };
154
+ }
@@ -0,0 +1,83 @@
1
+ import { createBigTempFile, runDownloadFile, runPythonSoftware, runSoftware, runUploadFile, runUploadTemplate } from './template';
2
+ import { initNetworkCheck } from './network_check';
3
+ import { testCredentials } from './test_utils';
4
+ import { test, expect } from 'vitest';
5
+ import path from 'path';
6
+
7
+ test('check runUploadTemplate', async () => {
8
+ const { plEndpoint, plUser, plPassword } = testCredentials();
9
+ const { logger, client, terminate } = await initNetworkCheck(plEndpoint, plUser, plPassword);
10
+
11
+ const greeting = await runUploadTemplate(logger, client, 'Jason');
12
+
13
+ expect(greeting).toBe('Hello, Jason');
14
+
15
+ await terminate();
16
+ });
17
+
18
+ test('check runUploadFile', async () => {
19
+ const { plEndpoint, plUser, plPassword } = testCredentials();
20
+ const {
21
+ logger,
22
+ lsDriver,
23
+ uploadBlobClient,
24
+ client,
25
+ terminate,
26
+ } = await initNetworkCheck(plEndpoint, plUser, plPassword);
27
+
28
+ const { filePath } = await createBigTempFile();
29
+
30
+ const blob = await runUploadFile(
31
+ logger,
32
+ lsDriver,
33
+ uploadBlobClient,
34
+ client,
35
+ filePath,
36
+ );
37
+
38
+ expect(blob.type.name).toBe('Blob');
39
+
40
+ await terminate();
41
+ });
42
+
43
+ test('check runDownloadFile', async () => {
44
+ const { plEndpoint, plUser, plPassword } = testCredentials();
45
+ const {
46
+ logger,
47
+ lsDriver,
48
+ uploadBlobClient,
49
+ downloadClient,
50
+ client,
51
+ terminate,
52
+ } = await initNetworkCheck(plEndpoint, plUser, plPassword);
53
+
54
+ const filePath = path.join(__dirname, '..', '..', 'test_assets', 'answer.txt');
55
+
56
+ const content = await runDownloadFile(logger, client, lsDriver, uploadBlobClient, downloadClient, filePath);
57
+
58
+ expect(content).toBe('42');
59
+
60
+ await terminate();
61
+ });
62
+
63
+ test('check runSoftware', async () => {
64
+ const { plEndpoint, plUser, plPassword } = testCredentials();
65
+ const { client, terminate } = await initNetworkCheck(plEndpoint, plUser, plPassword);
66
+
67
+ const greeting = await runSoftware(client);
68
+
69
+ expect(greeting).toBe('Hello from go binary\n');
70
+
71
+ await terminate();
72
+ });
73
+
74
+ test('check runPythonSoftware', async () => {
75
+ const { plEndpoint, plUser, plPassword } = testCredentials();
76
+ const { client, terminate } = await initNetworkCheck(plEndpoint, plUser, plPassword);
77
+
78
+ const greeting = await runPythonSoftware(client, 'John');
79
+
80
+ expect(greeting).toBe('Hello, John!\n');
81
+
82
+ await terminate();
83
+ });