@skyramp/skyramp 1.0.0-sha.b2dfe11 → 1.2.2

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -17
  3. package/package.json +14 -5
  4. package/scripts/download-binary.js +189 -0
  5. package/src/classes/Asserts.d.ts +16 -0
  6. package/src/classes/Asserts.js +41 -0
  7. package/src/classes/AsyncScenario.d.ts +133 -0
  8. package/src/classes/AsyncScenario.js +324 -0
  9. package/src/classes/AsyncTestStatus.d.ts +172 -0
  10. package/src/classes/AsyncTestStatus.js +488 -0
  11. package/src/classes/DelayConfig.d.ts +4 -0
  12. package/src/classes/DelayConfig.js +25 -0
  13. package/src/classes/Endpoint.d.ts +2 -2
  14. package/src/classes/Endpoint.js +81 -43
  15. package/src/classes/GrpcEndpoint.d.ts +1 -1
  16. package/src/classes/GrpcEndpoint.js +24 -3
  17. package/src/classes/LoadTestConfig.d.ts +131 -0
  18. package/src/classes/LoadTestConfig.js +186 -0
  19. package/src/classes/Protocol.d.ts +5 -0
  20. package/src/classes/Protocol.js +8 -0
  21. package/src/classes/RequestV2.d.ts +30 -0
  22. package/src/classes/RequestV2.js +181 -0
  23. package/src/classes/RequestValue.d.ts +24 -0
  24. package/src/classes/RequestValue.js +113 -0
  25. package/src/classes/ResponseV2.d.ts +24 -0
  26. package/src/classes/ResponseV2.js +96 -0
  27. package/src/classes/ResponseValue.d.ts +21 -0
  28. package/src/classes/ResponseValue.js +93 -0
  29. package/src/classes/RestEndpoint.d.ts +11 -2
  30. package/src/classes/RestEndpoint.js +90 -5
  31. package/src/classes/RestParam.d.ts +4 -0
  32. package/src/classes/RestParam.js +32 -0
  33. package/src/classes/Scenario.d.ts +48 -0
  34. package/src/classes/Scenario.js +208 -0
  35. package/src/classes/SkyrampClient.d.ts +184 -4
  36. package/src/classes/SkyrampClient.js +774 -40
  37. package/src/classes/Step.d.ts +28 -0
  38. package/src/classes/Step.js +113 -0
  39. package/src/classes/TrafficConfig.d.ts +6 -0
  40. package/src/classes/TrafficConfig.js +28 -0
  41. package/src/function.js +46 -0
  42. package/src/index.d.ts +14 -1
  43. package/src/index.js +36 -3
  44. package/src/lib.js +6 -6
  45. package/src/utils.js +180 -20
  46. package/src/utils.d.ts +0 -5
@@ -1,24 +1,155 @@
1
1
  const lib = require('../lib');
2
- const yaml = require('js-yaml');
2
+ const koffi = require('koffi');
3
+ const TrafficConfig = require('./TrafficConfig');
4
+ const RequestV2 = require('./RequestV2');
5
+ const ResponseV2 = require('./ResponseV2');
3
6
 
7
+ const workerInfoType = koffi.struct({
8
+ container_name: 'char*',
9
+ error: 'char*',
10
+ });
11
+
12
+ const testerInfoType = koffi.struct({
13
+ tester_id: 'char*',
14
+ error: 'char*',
15
+ });
16
+
17
+ const testerGenerateType = koffi.struct({
18
+ generated_files: 'char*',
19
+ error: 'char*',
20
+ });
21
+
22
+ const contractResponseType = koffi.struct({
23
+ response: 'char*',
24
+ error: 'char*',
25
+ });
26
+
27
+ const WORKER_URL = "public.ecr.aws/j1n2c2p2/rampup/worker";
28
+ const WORKER_TAG = "latest";
29
+ const CONTAINER_PORT = 35142;
30
+
31
+ // k8s related
32
+ const { createTestDescriptionFromScenario, getYamlBytes, readDataFromFile, SKYRAMP_YAML_VERSION } = require('../utils');
4
33
  const applyLocalWrapper = lib.func('applyLocalWrapper', 'string', []);
5
- const addKubeconfigWrapper = lib.func('addKubeconfigWrapper', 'string', ['string', 'bool', 'string', 'string']);
6
- const deleteSkyrampWorkerWrapper = lib.func('deleteSkyrampWorkerWrapper', 'string', ['string'])
7
- const deploySkyrampWorkerWrapper = lib.func('deploySkyrampWorkerWrapper', 'string', ['string', 'string', 'bool']);
34
+ const addKubeconfigWrapper = lib.func('addKubeconfigWrapper', 'string', ['string', 'string', 'string']);
35
+ const deleteSkyrampWorkerWrapper = lib.func('deleteSkyrampWorkerWrapper', 'string', ['string', 'string', 'string', 'string'])
36
+ const deploySkyrampWorkerWrapper = lib.func('deploySkyrampWorkerWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'bool']);
8
37
  const getKubeConfigPath = lib.func('getKubeConfigPath', 'string', []);
9
38
  const removeLocalWrapper = lib.func('removeLocalWrapper', 'string', []);
10
39
  const removeClusterFromConfigWrapper = lib.func('removeClusterFromConfigWrapper', 'string', ['string']);
11
- const applyMockDescriptionWrapper = lib.func('applyMockDescriptionWrapper', 'string', ['string', 'string']);
40
+ // docker related
41
+ const newStartDockerSkyrampWorkerWrapper = lib.func('newStartDockerSkyrampWorkerWrapper', workerInfoType, ['string', 'string', 'int', 'string', 'string']);
42
+ const newDeleteDockerSkyrampWorkerWrapper = lib.func('newDeleteDockerSkyrampWorkerWrapper', 'string', ['string']);
43
+ // tester related
44
+ const runTesterStartWrapper = lib.func('runTesterStartWrapper', testerInfoType, ['string', 'string', 'string', 'string', 'string', 'string', 'bool']);
45
+ // mocker related
46
+ const runTesterStartWrapperv1 = lib.func('runTesterStartWrapperWithGlobalHeaders', testerInfoType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'bool', 'string', 'string', 'string']);
47
+ const applyMockDescriptionWrapper = lib.func('applyMockDescriptionWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string']);
48
+ // NPM only: for VS code extension use
49
+ const initTargetWrapper = lib.func('initTargetWrapper', 'string', ['string']);
50
+ const deployTargetWrapper = lib.func('deployTargetWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'bool']);
51
+ const deleteTargetWrapper = lib.func('deleteTargetWrapper', 'string', ['string', 'string', 'string', 'string', 'string']);
52
+ const runTesterGenerateRestWrapper = lib.func('runTesterGenerateRestWrapper', testerGenerateType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'int', 'bool', 'bool', 'bool']);
53
+
54
+ const generateRestTestWrapper = lib.func('generateRestTestWrapper', 'string', ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'bool', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'string', 'bool', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'string', 'string']);
55
+ const traceCollectWrapper = lib.func('traceCollectWrapper', 'string', ['string', 'string', 'bool', 'string']);
56
+ const analyzeOpenapiWrapper = lib.func('analyzeOpenapiWrapper', 'string', ['string', 'string']);
57
+
58
+ // Load test scenario support
59
+ const sendScenarioWrapper = lib.func('sendScenarioWrapper', contractResponseType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool', 'bool', 'bool']);
60
+
61
+ // contract test
62
+ // func sendRequestWrapper(address, namespace, kubePath, kubeContext, clusterName, request, dockerNetwork, workerImage *C.char, localImage bool) *C.char {
63
+ const sendRequestWrapper = lib.func('sendRequestWrapper', contractResponseType, ['string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'bool']);
12
64
 
65
+ // dashboard
66
+ const deployDockerDashboardWrapper = lib.func('deployDockerDashboardWrapper', 'string', ['string']);
67
+ const deployK8sDashboardWrapper = lib.func('deployK8sDashboardWrapper', 'string', ['string']);
68
+
69
+ /**
70
+ * The `SkyrampClient` class is the main client that provides methods for interacting with the Skyramp service.
71
+ * It allows users to manage Kubernetes clusters, deploy and delete workers and targets, apply mock descriptions, and start tests.
72
+ *
73
+ * @class
74
+ * @name SkyrampClient
75
+ */
13
76
  class SkyrampClient {
14
- constructor(kubeconfigPath, clusterName, context, ingress) {
15
- this.workerNamespaces = [];
16
- this.mockedEndpoints = [];
17
- if (kubeconfigPath || clusterName || context || ingress) {
18
- this.addKubeconfig(clusterName, context, ingress, kubeconfigPath);
77
+ /**
78
+ * Initializes a new instance of the SkyrampClient class.
79
+ * @constructor
80
+ * @param {string|Object} kubeconfigPathOrOptions - Either the path to kubeconfig file or an options object
81
+ * @param {string} [kubeconfigPathOrOptions.kubeconfigPath] - The path to the kubeconfig file
82
+ * @param {string} [kubeconfigPathOrOptions.clusterName] - The name of the cluster
83
+ * @param {string} [kubeconfigPathOrOptions.context] - The context of the cluster
84
+ * @param {string} [kubeconfigPathOrOptions.userToken] - The user token for authentication
85
+ * @param {string} [kubeconfigPathOrOptions.address] - The address of the service
86
+ * @param {string} [kubeconfigPathOrOptions.dockerSkyrampPort] - The docker port for skyramp worker
87
+ * @param {string} [kubeconfigPathOrOptions.dockerNetwork] - The docker network for skyramp worker
88
+ * @param {string} [kubeconfigPathOrOptions.workerImage=""] - The worker image to use
89
+ * @param {string} [kubeconfigPathOrOptions.local_image=false] - The flag to indicate if the worker image is local
90
+ * @param {string} [clusterName] - The name of the cluster (when using individual parameters)
91
+ * @param {string} [context] - The context of the cluster (when using individual parameters)
92
+ * @param {string} [userToken] - The user token for authentication (when using individual parameters)
93
+ * @param {string} [directory=process.cwd()] - The working directory of the client.
94
+ */
95
+ constructor(kubeconfigPathOrOptions, clusterName, context, userToken, directory = process.cwd()) {
96
+ this.local_image = false;
97
+ if (typeof kubeconfigPathOrOptions === 'object') {
98
+ const options = kubeconfigPathOrOptions;
99
+ this.workerNamespaces = [];
100
+ this.userToken = options.userToken;
101
+ this.address = options.address;
102
+ // set docker params
103
+ if (options.runtime == "docker") {
104
+ if (options.dockerSkyrampPort) {
105
+ this.address = `localhost:${options.dockerSkyrampPort}`;
106
+ }
107
+ if (options.dockerNetwork) {
108
+ this.dockerNetwork = options.dockerNetwork;
109
+ }
110
+ }
111
+ // set k8s params
112
+ if (options.runtime == "k8s") {
113
+ if (options.k8SNamespace) {
114
+ this.namespace = options.k8SNamespace;
115
+ }
116
+ if (options.clusterName) {
117
+ this.clusterName = options.clusterName;
118
+ }
119
+ if (options.k8SContext) {
120
+ this.context = options.k8SContext;
121
+ }
122
+ if (options.k8SConfigPath) {
123
+ this.kubeconfigPath = options.k8SConfigPath;
124
+ }
125
+ }
126
+
127
+ if (this.kubeconfigPath || this.clusterName || this.context) {
128
+ this.addKubeConfig(this.context, this.clusterName, this.kubeconfigPath);
129
+ }
130
+ // set default worker image
131
+ if (options.workerImage) {
132
+ this.workerImage = options.workerImage;
133
+ }
134
+ if (options.local_image) {
135
+ this.local_image = options.local_image;
136
+ }
137
+ } else {
138
+ if (kubeconfigPathOrOptions || clusterName || context) {
139
+ this.addKubeConfig(context, clusterName, kubeconfigPathOrOptions);
140
+ }
141
+ this.workerNamespaces = [];
142
+ this.userToken = userToken;
143
+ this.projectPath = directory;
144
+ this.failedResponses = [];
19
145
  }
20
146
  }
21
147
 
148
+ /**
149
+ * Applies local changes to the Kubernetes cluster configuration.
150
+ *
151
+ * @returns {Promise} A promise that resolves when the local changes are successfully applied, or rejects with an error if any error occurs.
152
+ */
22
153
  async applyLocal() {
23
154
  return new Promise((resolve, reject) => {
24
155
  applyLocalWrapper.async((err, res) => {
@@ -29,15 +160,23 @@ class SkyrampClient {
29
160
  } else {
30
161
  this.kubeConfigPath = getKubeConfigPath();
31
162
  if (this.kubeConfigPath == "") {
32
- reject(new Error("no kubeconfig found"));
163
+ reject(new Error("no kubeconfig found"));
33
164
  } else {
34
- resolve();
165
+ resolve();
35
166
  }
36
167
  }
37
168
  });
38
169
  });
39
170
  }
40
171
 
172
+ /**
173
+ * Adds a Kubernetes configuration to the SkyrampClient object.
174
+ *
175
+ * @param {string} context - The context of the Kubernetes configuration.
176
+ * @param {string} clusterName - The name of the Kubernetes cluster.
177
+ * @param {string} kubeConfigPath - The path to the kubeconfig file.
178
+ * @returns {Promise} - A Promise that resolves if the operation is successful or rejects with an error if an error occurs.
179
+ */
41
180
  async addKubeConfig(context, clusterName, kubeConfigPath) {
42
181
  return new Promise((resolve, reject) => {
43
182
  addKubeconfigWrapper.async(context, clusterName, kubeConfigPath, (err, res) => {
@@ -53,14 +192,11 @@ class SkyrampClient {
53
192
  });
54
193
  }
55
194
 
56
- async removeCluster(clusterName) {
57
- if (clusterName) {
58
- return this.removeClusterFromConfig(clusterName);
59
- } else {
60
- return this.removeLocal();
61
- }
62
- }
63
-
195
+ /**
196
+ * Removes the local configuration of the SkyrampClient.
197
+ *
198
+ * @returns {Promise<void>} A promise that resolves when the removal is successful, or rejects with an error if an error occurs during the removal process.
199
+ */
64
200
  async removeLocal() {
65
201
  return new Promise((resolve, reject) => {
66
202
  removeLocalWrapper.async((err, res) => {
@@ -75,7 +211,13 @@ class SkyrampClient {
75
211
  });
76
212
  }
77
213
 
78
- async removeClusterFromConfig(clusterName) {
214
+ /**
215
+ * Asynchronously removes a cluster from the configuration.
216
+ *
217
+ * @param {string} clusterName - The name of the cluster to be removed from the configuration.
218
+ * @returns {Promise} A Promise that resolves if the cluster is successfully removed, or rejects with an error if the removal fails.
219
+ */
220
+ async removeCluster(clusterName) {
79
221
  return new Promise((resolve, reject) => {
80
222
  removeClusterFromConfigWrapper.async(clusterName, (err, res) => {
81
223
  if (err) {
@@ -89,12 +231,18 @@ class SkyrampClient {
89
231
  });
90
232
  }
91
233
 
92
- async deploySkyrampWorker(namespace, workerImage='', localImage=false) {
93
- if (this.kubeConfigPath === null) {
94
- throw new Error('No cluster to deploy worker to.');
95
- }
234
+ /**
235
+ * Deploys a worker to a Kubernetes cluster.
236
+ *
237
+ * @param {string} namespace - The namespace where the worker will be deployed.
238
+ * @param {string} [workerImage=''] - The image of the worker to be deployed.
239
+ * @param {boolean} [localImage=false] - Indicates whether the worker image is local or not.
240
+ * @returns {Promise} - A Promise that resolves if deployment is successful, and rejects with an error if any error occurs during deployment.
241
+ * @throws {Error} - If there is no cluster to deploy the worker to.
242
+ */
243
+ async deploySkyrampWorker(namespace, workerImage = '', localImage = false, kubePath = '', kubeContext = '', clusterName = '') {
96
244
  return new Promise((resolve, reject) => {
97
- deploySkyrampWorkerWrapper.async(namespace, workerImage, localImage, (err, res) => {
245
+ deploySkyrampWorkerWrapper.async(namespace, kubePath, kubeContext, clusterName, workerImage, localImage, (err, res) => {
98
246
  if (err) {
99
247
  reject(err);
100
248
  } else if (res) {
@@ -107,7 +255,14 @@ class SkyrampClient {
107
255
  });
108
256
  }
109
257
 
110
- async deleteSkyrampWorker(namespace) {
258
+ /**
259
+ * Deletes a worker from a specified namespace in the SkyrampClient class.
260
+ *
261
+ * @param {string} namespace - The namespace from which the worker should be deleted.
262
+ * @returns {Promise} - A promise that resolves with no value upon successful deletion.
263
+ * @throws {Error} - If there is no cluster to delete the worker from or if there is no worker to delete from the specified namespace.
264
+ */
265
+ async deleteSkyrampWorker(namespace, kubePath = '', kubeContext = '', clusterName = '') {
111
266
  if (this.kubeConfigPath === null) {
112
267
  throw new Error('No cluster to delete worker from.');
113
268
  }
@@ -116,7 +271,7 @@ class SkyrampClient {
116
271
  throw new Error(`No worker to delete from ${namespace} namespace.`);
117
272
  }
118
273
  return new Promise((resolve, reject) => {
119
- deleteSkyrampWorkerWrapper.async(namespace, (err, res) => {
274
+ deleteSkyrampWorkerWrapper.async(namespace, kubePath, kubeContext, clusterName, (err, res) => {
120
275
  if (err) {
121
276
  reject(err);
122
277
  } else if (res) {
@@ -129,24 +284,603 @@ class SkyrampClient {
129
284
  });
130
285
  }
131
286
 
132
- async apply(namespace, endpoint) {
133
- try {
134
- const yamlContent = yaml.dump(endpoint.mockDescription);
135
- return new Promise((resolve, reject) => {
136
- applyMockDescriptionWrapper.async(namespace, yamlContent, (err, res) => {
287
+ /**
288
+ * Asynchronous method that runs a Docker worker using the provided image, tag, port, and network name.
289
+ *
290
+ * @param {string} [workerImage=WORKER_URL] - The URL of the Docker worker image.
291
+ * @param {string} [workerTag=WORKER_TAG] - The tag of the Docker worker image.
292
+ * @param {number} [hostPort=CONTAINER_PORT] - The port on the host machine to expose for the Docker worker.
293
+ * @param {string} targetNetworkName - The name of the network to connect the Docker worker to.
294
+ * @returns {Promise} - A Promise that resolves if the Docker worker starts successfully and rejects if there is an error starting the worker.
295
+ */
296
+ async runDockerSkyrampWorker(workerImage = WORKER_URL, workerTag = WORKER_TAG, hostPost = CONTAINER_PORT, targetNetworkName = "", testServiceAlias = "") {
297
+ return new Promise((resolve, reject) => {
298
+ newStartDockerSkyrampWorkerWrapper.async(workerImage, workerTag, hostPost, targetNetworkName, testServiceAlias, (err, res) => {
299
+ if (err) {
300
+ reject(err);
301
+ } else if (res.error) {
302
+ reject(new Error(res.error));
303
+ } else {
304
+ resolve(res.container_name);
305
+ }
306
+ });
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Asynchronous method that removes a Docker container running the Skyramp worker.
312
+ *
313
+ * @param {string} containerName - The name of the Docker container to be removed.
314
+ * @returns {Promise} A promise that resolves when the container is successfully removed.
315
+ * @throws {Error} If an error occurs during the removal process.
316
+ * @throws {Error} If a non-empty response is received.
317
+ */
318
+ async removeDockerSkyrampWorker(containerName) {
319
+ return new Promise((resolve, reject) => {
320
+ newDeleteDockerSkyrampWorkerWrapper.async(containerName, (err, res) => {
321
+ if (err) {
322
+ reject(err);
323
+ } else if (res) {
324
+ reject(new Error(res));
325
+ } else {
326
+ resolve();
327
+ }
328
+ });
329
+ });
330
+ }
331
+
332
+ async mockerApply(namespace, kubeConfig, kubeContext, clusterName, address, endpoint) {
333
+ const yamlContent = getYamlBytes(endpoint.mockDescription);
334
+ return this.applyMockDescription(namespace, kubeConfig, kubeContext, clusterName, address, yamlContent);
335
+ }
336
+
337
+ /**
338
+ * Applies a mock description to a specified namespace and address.
339
+ *
340
+ * @param {string} namespace - The namespace where the mock description will be applied.
341
+ * @param {string} address - The address to which the mock description will be applied.
342
+ * @param {object} response - The details of the mock response.
343
+ * @param {object} trafficConfig - The traffic configuration parameters.
344
+ * @returns {Promise} A Promise that resolves when the mock description is successfully applied.
345
+ *
346
+ */
347
+ async mockerApplyV1(...args) {
348
+ let namespace, kubeConfig, kubeContext, clusterName, address, response, trafficConfig;
349
+
350
+ if (args.length === 1 && typeof args[0] === 'object') {
351
+ const options = args[0];
352
+ namespace = options.namespace;
353
+ kubeConfig = options.kubeConfig;
354
+ kubeContext = options.kubeContext;
355
+ clusterName = options.clusterName;
356
+ address = options.address;
357
+ response = options.response;
358
+ trafficConfig = options.trafficConfig;
359
+ } else {
360
+ [namespace, kubeConfig, kubeContext, clusterName, address, response, trafficConfig] = args;
361
+ }
362
+
363
+ const mockDescription = this.getMockDescriptionv1(response);
364
+ if (trafficConfig instanceof TrafficConfig) {
365
+ mockDescription.mock = { ...mockDescription.mock, ...trafficConfig.toJson() }
366
+ }
367
+
368
+ const yamlContent = getYamlBytes(mockDescription);
369
+ return this.applyMockDescription(namespace, kubeConfig, kubeContext, clusterName, address, yamlContent);
370
+ }
371
+
372
+ // NPM only: for VS code extension use
373
+ async mockerApplyFromFile(namespace, kubeConfig, kubeContext, clusterName, address, filepath) {
374
+ const yamlContent = getYamlBytes(readDataFromFile(filepath)[0]);
375
+ return this.applyMockDescription(namespace, kubeConfig, kubeContext, clusterName, address, yamlContent);
376
+ }
377
+
378
+ async applyMockDescription(namespace, kubeConfig, kubeContext, clusterName, address, yamlContent) {
379
+ return new Promise((resolve, reject) => {
380
+ applyMockDescriptionWrapper.async(namespace, kubeConfig, kubeContext, clusterName, address, yamlContent, '', '', (err, res) => {
381
+ if (err) {
382
+ reject(err);
383
+ } else if (res) {
384
+ reject(new Error(res));
385
+ } else {
386
+ resolve();
387
+ }
388
+ });
389
+ });
390
+ }
391
+
392
+ async testerStart(namespace, kubePath, kubeContext, clusterName, address, scenario) {
393
+ const preparedScenario = scenario.prepareTestDescription();
394
+ const testDescription = createTestDescriptionFromScenario({ scenario: preparedScenario });
395
+ const testYamlContent = getYamlBytes(testDescription);
396
+ return this.runTesterStart(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, false);
397
+ }
398
+
399
+ /**
400
+ * Starts a test scenario in a specified namespace.
401
+ *
402
+ * @param {string|object} namespaceOrOptions - The namespace in which to start the test scenario or an options object.
403
+ * @param {string} [address] - The address of the target service to test.
404
+ * @param {object} [scenario] - The scenario object that defines the test steps and assertions.
405
+ * @param {string} [testName] - The name of the test.
406
+ * @param {object} [globalHeaders] - Optional global headers to be included in the test requests.
407
+ * @param {boolean} [generateTestReport] - Optional flag to generate a test report. Default is false.
408
+ * @param {boolean} [isDockerenv] - Optional flag to indicate if the test is running in a Docker environment. Default is false.
409
+ *
410
+ * @returns {Promise} - A promise that resolves when the test scenario is started.
411
+ */
412
+ async testerStartV1(options = {}) {
413
+ const {
414
+ namespace = "",
415
+ address = "",
416
+ scenario,
417
+ testName,
418
+ globalHeaders = null,
419
+ globalVars = null, // eslint-disable-line no-unused-vars
420
+ generateTestReport = false,
421
+ isDockerenv = false,
422
+ kubePath = "",
423
+ kubeContext = "",
424
+ clusterName = ""
425
+ } = typeof options === 'object' ? options : {
426
+ namespace: options,
427
+ address: arguments[1],
428
+ scenario: arguments[2],
429
+ testName: arguments[3],
430
+ globalHeaders: arguments[4] || null,
431
+ globalVars: arguments[5] || null,
432
+ generateTestReport: arguments[6] || false,
433
+ isDockerenv: arguments[7] || false
434
+ };
435
+
436
+ const scenarioToUse = scenario || this.scenario;
437
+ const preparedScenario = scenarioToUse ? scenarioToUse.prepareTestDescription() : null;
438
+
439
+ const testDescription = createTestDescriptionFromScenario({ ...options, scenario: preparedScenario });
440
+ const testYamlContent = getYamlBytes(testDescription);
441
+ const stringifiedHeaders = JSON.stringify(globalHeaders);
442
+
443
+ return this.runTesterStartv1(namespace,
444
+ kubePath,
445
+ kubeContext,
446
+ clusterName,
447
+ address,
448
+ testYamlContent,
449
+ testName,
450
+ stringifiedHeaders,
451
+ generateTestReport,
452
+ isDockerenv
453
+ );
454
+ }
455
+
456
+ // NPM only: for VS code extension use
457
+ async testerStartFromFile(namespace, kubePath, kubeContext, clusterName, address, filepath) {
458
+ const testDescription = readDataFromFile(filepath)[0]
459
+ const testYamlContent = getYamlBytes(testDescription);
460
+ return this.runTesterStart(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, true);
461
+ }
462
+
463
+ async runTesterStart(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, generateReport) {
464
+ return new Promise((resolve, reject) => {
465
+ runTesterStartWrapper.async(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, generateReport, (err, res) => {
466
+ if (err) {
467
+ reject(err);
468
+ } else if (res.error) {
469
+ reject(new Error(res.error));
470
+ } else {
471
+ resolve(res.tester_id);
472
+ }
473
+ });
474
+ });
475
+ }
476
+
477
+ async runTesterStartv1(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, testName, globalHeaders, generateReport = true) {
478
+ return new Promise((resolve, reject) => {
479
+ runTesterStartWrapperv1.async(namespace, kubePath, kubeContext, clusterName, address, testYamlContent, "", testName, globalHeaders, generateReport, true, "", "", "", (err, res) => {
480
+ if (err) {
481
+ reject(err);
482
+ } else if (res.error) {
483
+ reject(new Error(res.error));
484
+ } else {
485
+ resolve(res.tester_id);
486
+ }
487
+ });
488
+ });
489
+ }
490
+
491
+ // NPM only: for VS code extension use
492
+ async initTarget(taretName) {
493
+ return new Promise((resolve, reject) => {
494
+ initTargetWrapper.async(taretName, (err, res) => {
495
+ if (err) {
496
+ reject(err);
497
+ } else if (res) {
498
+ reject(new Error(res));
499
+ } else {
500
+ resolve();
501
+ }
502
+ });
503
+ });
504
+ }
505
+
506
+ // NPM only: for VS code extension use
507
+ async deployTarget(targetDescription, namespace, workerImage = '', localImage = false) {
508
+ if (this.kubeConfigPath === null) {
509
+ throw new Error('No cluster to deploy target to.');
510
+ }
511
+ return new Promise((resolve, reject) => {
512
+ deployTargetWrapper.async(targetDescription, namespace, '', '', '', workerImage, localImage, (err, res) => {
513
+ if (err) {
514
+ reject(err);
515
+ } else if (res) {
516
+ reject(new Error(res));
517
+ } else {
518
+ resolve();
519
+ }
520
+ });
521
+ });
522
+ }
523
+
524
+ // NPM only: for VS code extension use
525
+ async deleteTarget(targetDescription, namespace) {
526
+ if (this.kubeConfigPath === null) {
527
+ throw new Error('No cluster to delete target from.');
528
+ }
529
+ return new Promise((resolve, reject) => {
530
+ deleteTargetWrapper.async(targetDescription, namespace, '', '', '', (err, res) => {
531
+ if (err) {
532
+ reject(err);
533
+ } else if (res) {
534
+ reject(new Error(res));
535
+ } else {
536
+ resolve();
537
+ }
538
+ });
539
+ });
540
+ }
541
+
542
+ getMockDescriptionv1(responseOrResponses) {
543
+ const mockDescription = {
544
+ version: SKYRAMP_YAML_VERSION,
545
+ services: [],
546
+ endpoints: [],
547
+ responses: [],
548
+ mock: { responses: [] }
549
+ };
550
+
551
+ const processResponse = (response) => {
552
+ const endpoint = response?.endpointDescriptor?.endpoint;
553
+ const service = response?.endpointDescriptor?.services[0];
554
+
555
+ if (!mockDescription?.endpoints.some(ep => ep.name === endpoint.name)) {
556
+ mockDescription?.endpoints.push(endpoint);
557
+ }
558
+
559
+ if (!mockDescription?.services?.some(s => s?.name === service?.name)) {
560
+ mockDescription?.services?.push(service);
561
+ }
562
+
563
+ mockDescription?.responses?.push(response?.toJson());
564
+ let mockRes = { "responseName": response?.name }
565
+ if (response?.trafficConfig && response?.trafficConfig instanceof TrafficConfig) {
566
+ mockRes = { ...mockRes, ...response?.trafficConfig?.toJson() }
567
+ }
568
+ mockDescription?.mock?.responses?.push(mockRes);
569
+ };
570
+ if (Array.isArray(responseOrResponses)) {
571
+ responseOrResponses.forEach(processResponse);
572
+ } else {
573
+ processResponse(responseOrResponses);
574
+ }
575
+
576
+ return mockDescription;
577
+ }
578
+
579
+ /**
580
+ * Generates test scenarios based on the provided parameters.
581
+ *
582
+ * @param {Protocol} protocol - The protocol to be used.
583
+ * @param {string} apiSchemaPath - The path to the API schema.
584
+ * @param {string} alias - The alias for the generated tests.
585
+ * @param {string} endpointPath - Endpoint REST path to filter for.
586
+ * @param {Language} language - The programming language for the generated tests.
587
+ * @param {string} tag - Tag to filter OpenAPI endpoint paths for.
588
+ * @param {string} sampleRequestPath - The path to the sample request.
589
+ * @param {int} port - The port number.
590
+ * @param {boolean} generateRobot - Whether to generate robot tests.
591
+ * @param {boolean} functionalScenario - Whether to generate functional scenarios.
592
+ * @param {boolean} negativeScenario - Whether to generate negative scenarios.
593
+ * @returns {Promise<string[]>} A promise that resolves with a list of generated test file paths.
594
+ */
595
+ async testerGenerate(protocol, apiSchemaPath, alias, endpointPath, language, tag, sampleRequestPath, port, generateRobot, functionalScenario, negativeScenario) {
596
+ return new Promise((resolve, reject) => {
597
+ runTesterGenerateRestWrapper.async(protocol, apiSchemaPath, alias, endpointPath, language, tag, sampleRequestPath, this.projectPath, port, generateRobot, functionalScenario, negativeScenario, (err, res) => {
598
+ if (err) {
599
+ console.error(`Error generating tests: ${err.name} - ${err.message}`);
600
+ reject(err);
601
+ } else if (res.error) {
602
+ console.error(`Error generating tests: ${res.error}`);
603
+ reject(new Error(res.error));
604
+ } else {
605
+ console.log(`Test generation completed successfully. Generated files: ${res.generated_files}`);
606
+
607
+ const generatedTestNames = res.generated_files.split(',');
608
+ resolve(generatedTestNames);
609
+ }
610
+ });
611
+ });
612
+ }
613
+
614
+ /**
615
+ * Sends a request to a Skyramp worker using the V2 API
616
+ * @param {Object} options - The options for sending the request
617
+ * @param {string} [options.address=""] - The address of the worker to send the request to
618
+ * @param {string} [options.namespace=""] - The namespace where the worker is deployed
619
+ * @param {string} [options.kubePath=""] - The path to the kubeconfig file
620
+ * @param {string} [options.kubeContext=""] - The kubernetes context to use
621
+ * @param {string} [options.clusterName=""] - The name of the kubernetes cluster
622
+ * @param {string} [options.dockerNetwork=""] - The docker network where the worker is deployed
623
+ * @param {string} [options.workerImage=""] - The worker image to use
624
+ * @param {string} [options.local_image=""] - The flag to indicate if the worker image is local
625
+ * @returns {Promise<string>} A promise that resolves with the response from the worker
626
+ */
627
+ async sendRequest(options) {
628
+ const req = new RequestV2(options);
629
+ return new Promise((resolve, reject) => {
630
+ const jsonRequest = req.toJson();
631
+ sendRequestWrapper.async(this.address, this.namespace, this.kubeconfigPath, this.context, this.clusterName, jsonRequest, this.dockerNetwork, this.workerImage, this.clientID, this.local_image, (err, res) => {
632
+ if (err) {
633
+ reject(err);
634
+ } else if (res) {
635
+ if (res.error != null) {
636
+ if (res.response != null) {
637
+ const jsonResponse = JSON.parse(res.response);
638
+ const response = new ResponseV2(jsonResponse);
639
+ this.failedResponses.push(response)
640
+ }
641
+ reject(res.error);
642
+ } else if (res.response != null) {
643
+ const jsonResponse = JSON.parse(res.response);
644
+ const response = new ResponseV2(jsonResponse);
645
+ if (response.error != "") {
646
+ this.failedResponses.push(response)
647
+ }
648
+ resolve(response);
649
+ } else {
650
+ reject(new Error('failed to send request'));
651
+ }
652
+ } else {
653
+ reject(new Error('failed to send request'));
654
+ }
655
+ });
656
+ });
657
+ }
658
+
659
+ isSuccess() {
660
+ return this.failedResponses.length == 0
661
+ }
662
+
663
+ getFailedResponses() {
664
+ return this.failedResponses
665
+ }
666
+
667
+ async deployDashboard(network) {
668
+ if (network === undefined || network === null || network === "") {
669
+ network = "skyramp-dashboard"
670
+ }
671
+
672
+ if (this.kubeconfigPath !== null || this.kubeconfigPath !== undefined) {
673
+ network = "skyramp"
674
+ }
675
+
676
+ return new Promise((resolve, reject) => {
677
+ if (this.kubeconfigPath === null || this.kubeconfigPath === undefined) {
678
+ deployDockerDashboardWrapper.async(network, (err, res) => {
137
679
  if (err) {
138
680
  reject(err);
139
- } else if (res) {
140
- reject(new Error(res));
141
681
  } else {
142
- resolve();
143
- this.mockedEndpoints.push(endpoint);
682
+ resolve(res);
144
683
  }
145
684
  });
146
- });
147
- } catch (err) {
148
- throw new Error('Error converting to YAML bytes:', err);
685
+ } else {
686
+ deployK8sDashboardWrapper.async(network, (err, res) => {
687
+ if (err) {
688
+ reject(err);
689
+ } else {
690
+ resolve(res);
691
+ }
692
+ });
693
+ }
694
+ });
695
+ }
696
+
697
+ /**
698
+ * Analyzes an OpenAPI schema and URI.
699
+ *
700
+ * @param {Object} options - The options for analysis.
701
+ * @param {string} options.apiSchema - The OpenAPI schema to analyze.
702
+ * @param {string} options.uri - The URI to analyze.
703
+ * @returns {Promise<string>} A promise that resolves with the analysis result.
704
+ */
705
+ async analyzeOpenapi(options) {
706
+ return new Promise((resolve, reject) => {
707
+ analyzeOpenapiWrapper.async(
708
+ options.apiSchema || "",
709
+ options.uri || "",
710
+ (err, res) => {
711
+ if (err) {
712
+ reject(err);
713
+ } else if (res.error) {
714
+ reject(new Error(res));
715
+ } else {
716
+ resolve(res);
717
+ }
718
+ }
719
+ );
720
+ });
721
+ }
722
+
723
+ async traceCollect(options) {
724
+ return new Promise((resolve, reject) => {
725
+ traceCollectWrapper.async(
726
+ options.output || "",
727
+ options.workerContainerName || "",
728
+ options.playwright || false,
729
+ options.playwrightOutput || "",
730
+ (err, res) => {
731
+ if (err) {
732
+ reject(err);
733
+ } else {
734
+ resolve(res);
735
+ }
736
+ }
737
+ );
738
+ });
739
+ }
740
+
741
+ async generateRestTest(options) {
742
+ return new Promise((resolve, reject) => {
743
+ generateRestTestWrapper.async(
744
+ options.testType || "",
745
+ options.uri || "",
746
+ options.method || "",
747
+ options.language || "",
748
+ options.framework || "",
749
+ options.output || "",
750
+ options.outputDir || "",
751
+ options.runtime || "",
752
+ options.dockerNetwork || "",
753
+ options.dockerWorkerPort || 0,
754
+ options.k8sNamespace || "",
755
+ options.k8sConfig || "",
756
+ options.k8sContext || "",
757
+ options.authHeader || "",
758
+ options.authType || "",
759
+ options.requestData || "",
760
+ options.responseData || "",
761
+ options.responseStatusCode || "",
762
+ options.force || false,
763
+ options.deployDashboard || false,
764
+ options.formParams || "",
765
+ options.pathParams || "",
766
+ options.queryParams || "",
767
+ JSON.stringify(options.apiSchema || []),
768
+ options.traceFilePath || "",
769
+ JSON.stringify(options.generateInclude || []),
770
+ JSON.stringify(options.generateExclude || []),
771
+ JSON.stringify(options.generateNoProxy || []),
772
+ options.generateInsecure || false,
773
+ options.assertOption || "",
774
+ options.playwright || false,
775
+ options.playwrightOutput || "",
776
+ options.playwrightInput || "",
777
+ options.loadCount || "0",
778
+ options.loadDuration || "0",
779
+ options.loadNumThreads || "0",
780
+ options.loadRampupDuration || "0",
781
+ options.loadRampupInterval || "0",
782
+ options.loadTargetRPS || "0",
783
+ options.unblock || false,
784
+ options.entrypoint || "",
785
+ options.accessToken || "",
786
+ (err, res) => {
787
+ if (err) {
788
+ reject(err);
789
+ } else {
790
+ resolve(res);
791
+ }
792
+ }
793
+ );
794
+ });
795
+ }
796
+
797
+ /**
798
+ * Sends a scenario for load testing using the V2 API
799
+ * @param {Object|Array} scenario - The scenario object or array of scenarios to run
800
+ * @param {Object} [options={}] - Additional options for the load test
801
+ * @param {string} [options.until] - The condition to terminate the test
802
+ * @param {LoadTestConfig} [options.loadTestConfig] - The load test configuration
803
+ * @param {string} [options.dependenciesFilepath=""] - The path to the dependencies file
804
+ * @param {boolean} [options.blocked=true] - Whether to block until test completion
805
+ * @param {boolean} [options.debug=true] - Whether to print debug information
806
+ * @param {boolean} [options.skipCertVerification=false] - Whether to skip SSL verification
807
+ * @returns {Promise<AsyncTestStatus>} A promise that resolves with the async test status
808
+ */
809
+ async sendScenario(scenario, options = {}) {
810
+ const {
811
+ until,
812
+ loadTestConfig,
813
+ dependenciesFilepath = "",
814
+ blocked = true,
815
+ debug = true,
816
+ skipCertVerification = false
817
+ } = options;
818
+
819
+ if (debug) { /* add to api for future use */}
820
+ let configJson = "";
821
+ if (loadTestConfig) {
822
+ configJson = loadTestConfig.toJson();
149
823
  }
824
+
825
+ // Ensure scenario is an array
826
+ const scenarioList = Array.isArray(scenario) ? scenario : [scenario];
827
+
828
+ // Process scenarios to match expected AsyncScenario format
829
+ const processedScenarios = scenarioList.map(_scenario => {
830
+ // If scenario has toJson method, use it for proper formatting
831
+ if (typeof _scenario.toJson === 'function') {
832
+ const scenarioJson = _scenario.toJson();
833
+
834
+ return {
835
+ ...scenarioJson,
836
+ until: until || scenarioJson.until
837
+ };
838
+ }
839
+
840
+ // Fallback for scenarios without toJson method
841
+ return {
842
+ name: _scenario.name,
843
+ steps: _scenario.steps || [],
844
+ vars: _scenario.vars || {},
845
+ until: until || _scenario.until || "",
846
+ maxRetries: _scenario.maxRetries || 5,
847
+ interval: _scenario.retryInterval || 1,
848
+ ignoreFailure: _scenario.ignoreFailure || false
849
+ };
850
+ });
851
+
852
+ return new Promise((resolve, reject) => {
853
+ sendScenarioWrapper.async(
854
+ this.address || "",
855
+ this.dockerNetwork || "",
856
+ this.namespace || "",
857
+ this.kubeconfigPath || "",
858
+ this.context || "",
859
+ this.clusterName || "",
860
+ JSON.stringify(processedScenarios),
861
+ "",
862
+ configJson,
863
+ dependenciesFilepath,
864
+ this.workerImage || "",
865
+ blocked,
866
+ this.local_image || false,
867
+ skipCertVerification,
868
+ (err, res) => {
869
+ if (err) {
870
+ reject(err);
871
+ } else if (res.error) {
872
+ reject(new Error(res.error));
873
+ } else if (res.response) {
874
+ const AsyncTestStatus = require('./AsyncTestStatus');
875
+ const jsonResponse = JSON.parse(res.response);
876
+ const testStatus = AsyncTestStatus.create(jsonResponse);
877
+ resolve(testStatus);
878
+ } else {
879
+ reject(new Error('Failed to send scenario'));
880
+ }
881
+ }
882
+ );
883
+ });
150
884
  }
151
885
  }
152
886