@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
@@ -0,0 +1,28 @@
1
+ import Scenario from './Scenario';
2
+ import Asserts from './Asserts';
3
+ import RequestValue from './RequestValue';
4
+
5
+ export interface StepConfig {
6
+ stepName: string;
7
+ maxRetries?: number;
8
+ interval?: string;
9
+ until?: string;
10
+ blobOverride?: {[blobName: string]: string};
11
+ override?: {[varName: string]: string | number};
12
+ description?: string;
13
+ break?: boolean;
14
+ ignore?: boolean;
15
+ if_?: string;
16
+ export?: {[exportName: string]: string | number};
17
+ untilFunc?: CallableFunction;
18
+ wait?: string;
19
+ with_?: string;
20
+ }
21
+
22
+ export interface StepOptions extends StepConfig {
23
+ step: Scenario | Asserts | RequestValue;
24
+ }
25
+
26
+ export declare class Step {
27
+ constructor(options: StepOptions);
28
+ }
@@ -0,0 +1,113 @@
1
+
2
+ const RequestValue = require('./RequestValue');
3
+ const Asserts = require('./Asserts');
4
+
5
+ /**
6
+ * Represents a step in a scenario.
7
+ * @class
8
+ */
9
+ class Step {
10
+ /**
11
+ * Initialize a new instance of a Step.
12
+ * @constructor
13
+ * @param {Object} options - The options for initializing the Step object.
14
+ * @param {Object} options.step - The step object, which can be an instance of RequestValue, Asserts, or Scenario.
15
+ * @param {number} [options.maxRetries=0] - The maximum number of retries for the step.
16
+ * @param {number} [options.interval] - The interval between retries.
17
+ * @param {string} [options.until] - The condition to wait until.
18
+ * @param {Object} [options.blobOverride] - The blob override for the step.
19
+ * @param {Object} [options.override] - The override for the step.
20
+ * @param {string} [options.description] - The description of the step.
21
+ * @param {string} [options.stepName] - The name of the step.
22
+ * @param {boolean} [options.break] - Whether to break the step.
23
+ * @param {boolean} [options.ignore] - Whether to ignore the step.
24
+ * @param {boolean} [options.if_] - The condition to check before executing the step.
25
+ * @param {boolean} [options.export] - Whether to export the step.
26
+ * @param {Function} [options.untilFunc] - The function to call until the condition is met.
27
+ * @param {number} [options.wait] - The wait time for the step.
28
+ * @param {Object} [options.with_] - Additional options for the step.
29
+ */
30
+ constructor(options) {
31
+ const { step,
32
+ maxRetries,
33
+ interval,
34
+ until,
35
+ blobOverride,
36
+ override,
37
+ description,
38
+ stepName,
39
+ break: breakStep,
40
+ ignore,
41
+ if_,
42
+ export: exportStep,
43
+ untilFunc,
44
+ wait,
45
+ with_ } = options;
46
+ this.step = step;
47
+ this.maxRetries = maxRetries;
48
+ if (interval) this.interval = interval;
49
+ if (until) this.until = until;
50
+ if (blobOverride) this.blobOverride = blobOverride;
51
+ if (override) this.override = override;
52
+ this.description = description;
53
+ this.stepName = stepName;
54
+ if (breakStep) this.break = breakStep;
55
+ if (ignore) this.ignore = ignore;
56
+ if (if_) this.if = if_;
57
+ if (exportStep) this.export = exportStep;
58
+ if (untilFunc) this.untilFunc = untilFunc;
59
+ if (wait) this.wait = wait;
60
+ if (with_) this.with = with_;
61
+ }
62
+
63
+ getRepeatConfig() {
64
+ return {
65
+ ...(this.maxRetries !== undefined && { maxRetries: this.maxRetries }),
66
+ ...(this.interval !== undefined && { interval: this.interval }),
67
+ ...(this.until !== undefined && { until: this.until }),
68
+ ...(this.untilFunc !== undefined && { untilFunc: this.untilFunc }),
69
+ ...(this.with !== undefined && { with: this.with }),
70
+ }
71
+ }
72
+ /**
73
+ * Convert the step to a JSON object.
74
+ * @returns {Object} The JSON representation of the step.
75
+ */
76
+ toJson() {
77
+ const step = {};
78
+ try {
79
+ if (this.step instanceof RequestValue) {
80
+ step.requestName = this.step.name;
81
+ } else if (this.step instanceof Asserts) {
82
+ step.asserts = this.step.toAssert();
83
+ } else {
84
+ if (this.step && this.step.constructor && this.step.constructor.name === 'Scenario') {
85
+ step.scenarioName = this.step.name;
86
+ }
87
+ }
88
+ } catch (error) {
89
+ console.error(error);
90
+ }
91
+
92
+ const additional = {
93
+ description: this.description,
94
+ name: this.stepName,
95
+ ...(this.blobOverride !== undefined && { blobOverride: this.blobOverride }),
96
+ ...(this.break !== undefined && { break: this.break }),
97
+ ...(this.export !== undefined && { export: this.export }),
98
+ ...(this.if !== undefined && { if: this.if }),
99
+ ...(this.ignore !== undefined && { ignore: this.ignore }),
100
+ ...(this.override !== undefined && { override: this.override }),
101
+ ...(this.untilFunc !== undefined && { untilFunc: this.untilFunc }),
102
+ ...(this.wait !== undefined && { wait: this.wait }),
103
+ };
104
+
105
+ return {
106
+ ...step,
107
+ ...additional,
108
+ ...(this.maxRetries !== undefined && { repeat: this.getRepeatConfig() }),
109
+ };
110
+ }
111
+ }
112
+
113
+ module.exports = Step;
@@ -0,0 +1,6 @@
1
+ import { DelayConfig } from './DelayConfig';
2
+
3
+ export declare class TrafficConfig {
4
+ constructor(lossPercentage: int, delayConfig: DelayConfig);
5
+ toJson() : JSON;
6
+ }
@@ -0,0 +1,28 @@
1
+ const DelayConfig = require('./DelayConfig');
2
+ /**
3
+ * Represents a configuration for traffic values.
4
+ */
5
+ class TrafficConfig {
6
+ /**
7
+ * Initializes a new TrafficConfig object with the specified loss percentage and delay configuration.
8
+ * @constructor
9
+ * @param {number} lossPercentage - The loss percentage for traffic configuration.
10
+ * @param {DelayConfig} delayConfig - The delay configuration for traffic. It is an optional field and can be set using an instance of the DelayConfig class.
11
+ */
12
+ constructor(lossPercentage, delayConfig) {
13
+ this.lossPercentage = lossPercentage;
14
+ this.delayConfig = delayConfig;
15
+ }
16
+
17
+ toJson() {
18
+ let trafficConfig = {
19
+ lossPercentage: this.lossPercentage,
20
+ };
21
+ if (this.delayConfig && this.delayConfig instanceof DelayConfig) {
22
+ trafficConfig.delayConfig = this.delayConfig.toJson();
23
+ }
24
+ return trafficConfig;
25
+ }
26
+ }
27
+
28
+ module.exports = TrafficConfig;
@@ -0,0 +1,46 @@
1
+ const ResponseV2 = require('./classes/ResponseV2');
2
+ const lib = require('./lib');
3
+
4
+ // func checkStatusCodeWrapper(actualStatus C.int, expectedStatus *C.char) (*C.char, *C.char) {
5
+ const checkStatusCodeWrapper = lib.func('checkStatusCodeWrapper', 'int', ['int', 'string']);
6
+
7
+
8
+ /**
9
+ * Checks if the response's status code matches the expected status code (with support for wildcards).
10
+ *
11
+ * The expected status can include 'x' as a wildcard for a single digit.
12
+ * Examples:
13
+ * - "200" matches exactly 200
14
+ * - "20x" matches any status code in the range 200 to 209
15
+ * - "2xx" matches any status code in the range 200 to 299
16
+ *
17
+ * @param {ResponseV2} response - The response object containing a `status_code` attribute.
18
+ * @param {string} expectedStatus - The expected status code as a string with optional 'x' wildcards.
19
+ * @returns {boolean} True if the status code matches, false otherwise.
20
+ * @throws {Error} If the response object lacks a 'status_code' attribute.
21
+ */
22
+ function checkStatusCode(response, expectedStatus) {
23
+ if (!(response instanceof ResponseV2)) {
24
+ throw new Error('Response must be an instance of ResponseV2');
25
+ }
26
+
27
+ if (!expectedStatus || typeof expectedStatus !== 'string') {
28
+ throw new Error('Expected status must be a string');
29
+ }
30
+
31
+ try {
32
+ const result = checkStatusCodeWrapper(response.statusCode, expectedStatus);
33
+ if (result == 1) {
34
+ return true
35
+ }
36
+ return false
37
+
38
+ } catch (err) {
39
+ console.error(`Error in checkStatusCode: ${err.message}`);
40
+ throw err; // Re-throw to allow caller to handle errors
41
+ }
42
+ }
43
+
44
+ module.exports = {
45
+ checkStatusCode
46
+ };
package/src/index.d.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  export * from './classes/Endpoint';
2
2
  export * from './classes/GrpcEndpoint';
3
3
  export * from './classes/RestEndpoint';
4
+ export * from './classes/Scenario';
4
5
  export * from './classes/SkyrampClient';
5
- export * from './utils';
6
+ export * from './classes/RequestValue';
7
+ export * from './classes/ResponseValue';
8
+ export * from './classes/RestParam';
9
+ export * from './classes/TrafficConfig';
10
+ export * from './classes/DelayConfig';
11
+ export * from './classes/Protocol';
12
+ export * from './classes/RequestV2';
13
+ export * from './classes/ResponseV2';
14
+ export * from './classes/AsyncScenario';
15
+ export * from './classes/LoadTestConfig';
16
+ export * from './classes/AsyncTestStatus';
17
+ export * from './utils';
18
+ export * from './function';
package/src/index.js CHANGED
@@ -1,11 +1,44 @@
1
1
  const SkyrampClient = require('./classes/SkyrampClient');
2
2
  const GrpcEndpoint = require('./classes/GrpcEndpoint');
3
3
  const RestEndpoint = require('./classes/RestEndpoint');
4
- const { runTesterStart } = require('./utils');
4
+ const Scenario = require('./classes/Scenario');
5
+ const RequestValue = require('./classes/RequestValue');
6
+ const ResponseValue = require('./classes/ResponseValue');
7
+ const RestParam = require('./classes/RestParam');
8
+ const TrafficConfig = require('./classes/TrafficConfig');
9
+ const DelayConfig = require('./classes/DelayConfig');
10
+ const Protocol = require('./classes/Protocol');
11
+ const Asserts= require('./classes/Asserts');
12
+ const Step = require('./classes/Step');
13
+ const RequestV2 = require('./classes/RequestV2');
14
+ const ResponseV2 = require('./classes/ResponseV2');
15
+ const { AsyncScenario, AsyncRequest } = require('./classes/AsyncScenario');
16
+ const { LoadTestConfig } = require('./classes/LoadTestConfig');
17
+ const AsyncTestStatus = require('./classes/AsyncTestStatus');
18
+ const { getValue, checkSchema, iterate } = require('./utils');
19
+ const { checkStatusCode } = require('./function');
5
20
 
6
21
  module.exports = {
7
22
  SkyrampClient,
23
+ RequestV2,
24
+ ResponseV2,
8
25
  GrpcEndpoint,
9
26
  RestEndpoint,
10
- runTesterStart
11
- }
27
+ Scenario,
28
+ RequestValue,
29
+ ResponseValue,
30
+ RestParam,
31
+ TrafficConfig,
32
+ DelayConfig,
33
+ Protocol,
34
+ Asserts,
35
+ Step,
36
+ AsyncScenario,
37
+ AsyncRequest,
38
+ LoadTestConfig,
39
+ AsyncTestStatus,
40
+ getValue,
41
+ checkStatusCode,
42
+ checkSchema,
43
+ iterate,
44
+ }
package/src/lib.js CHANGED
@@ -7,18 +7,18 @@ let libPath = '';
7
7
 
8
8
  if (platform === 'linux') {
9
9
  if (arch === 'x64') {
10
- libPath = 'dev-linux-amd64.so';
10
+ libPath = 'skyramp-linux-amd64.so';
11
11
  } else if (arch === 'ia32') {
12
- libPath = 'dev-linux-386.so';
12
+ libPath = 'skyramp-linux-386.so';
13
13
  }
14
14
  } else if (platform === 'darwin') {
15
15
  if (arch === 'x64') {
16
- libPath = 'dev-darwin-amd64.dylib';
16
+ libPath = 'skyramp-darwin-amd64.dylib';
17
17
  } else if (arch === 'arm64') {
18
- libPath = 'dev-darwin-arm64.dylib';
18
+ libPath = 'skyramp-darwin-arm64.dylib';
19
19
  }
20
20
  } else if (platform === 'win32') {
21
- libPath = 'dev-windows-amd64.dll';
21
+ libPath = 'skyramp-windows-amd64.dll';
22
22
  }
23
23
 
24
24
  if (!libPath) {
@@ -27,4 +27,4 @@ if (!libPath) {
27
27
  const libFullPath = path.join(__dirname, '..', 'lib', libPath);
28
28
  const lib = koffi.load(libFullPath);
29
29
 
30
- module.exports = lib;
30
+ module.exports = lib;
package/src/utils.js CHANGED
@@ -1,22 +1,63 @@
1
1
  const fs = require('fs');
2
- const lib = require('./lib');
2
+ const koffi = require('koffi');
3
3
  const path = require('path');
4
4
  const yaml = require('js-yaml');
5
+ const lib = require('./lib');
6
+ const SKYRAMP_YAML_VERSION = "v1"
5
7
 
6
- runTesterStartWrapper = lib.func('runTesterStartWrapper', 'string', ['string', 'string']);
7
-
8
- async function runTesterStart(namespace, testDescription) {
9
- return new Promise((resolve, reject) => {
10
- runTesterStartWrapper.async(namespace, testDescription, (err, res) => {
11
- if (err) {
12
- reject(err);
13
- } else if (res) {
14
- reject(new Error(res));
15
- } else {
16
- resolve();
17
- }
18
- });
19
- });
8
+ const responseType = koffi.struct({
9
+ response: 'char*',
10
+ error: 'char*',
11
+ });
12
+
13
+ const getJSONValueWrapper = lib.func('getJSONValueWrapper', responseType, ['string', 'string']);
14
+ const checkSchemaWrapper = lib.func('checkSchemaWrapper', responseType, ['string', 'string']);
15
+ const iterateWrapper = lib.func('generateJsonTreePathsFromBlob', 'string', ['string']);
16
+
17
+ function isJSONString(value) {
18
+ if (typeof value !== "string") {
19
+ return false; // It's not a string, so it can't be JSON
20
+ }
21
+
22
+ try {
23
+ const parsed = JSON.parse(value);
24
+ return typeof parsed === "object" && parsed !== null;
25
+ } catch (e) {
26
+ return false; // JSON.parse failed, so it's not a JSON string
27
+ }
28
+ }
29
+
30
+ function safeStringify(value) {
31
+ return typeof value === "string" && isJSONString(value) ? value : JSON.stringify(value);
32
+ }
33
+
34
+ function getYamlBytes(jsonObject) {
35
+ try {
36
+ const yamlContent = yaml.dump(jsonObject, { lineWidth: 100 });
37
+ return yamlContent;
38
+ } catch (error) {
39
+ throw new Error('Error converting to YAML bytes:', error);
40
+ }
41
+ }
42
+
43
+ function createTestDescriptionFromScenario({ scenario, globalVars }) {
44
+ const { name, startsAt = 1, scenarios, services, endpoints, requests } = scenario;
45
+ const testDescription = {
46
+ version: SKYRAMP_YAML_VERSION,
47
+ test: {
48
+ ...(globalVars !== undefined && { globalVars }),
49
+ testPattern: [{
50
+ startAt: startsAt,
51
+ scenarioName: name
52
+ }]
53
+ },
54
+ services: services,
55
+ endpoints: endpoints,
56
+ scenarios: scenarios,
57
+ requests: requests
58
+ };
59
+
60
+ return testDescription;
20
61
  }
21
62
 
22
63
  function readDataFromFile(filename) {
@@ -34,14 +75,133 @@ function readDataFromFile(filename) {
34
75
  } else {
35
76
  throw new Error('Unsupported file format. Only .json, .yaml, and .js files are supported.');
36
77
  }
37
- const jsonString = JSON.stringify(jsonData, null, 2)
38
- return [jsonString, false];
39
- } catch(error) {
78
+ return [jsonData, false];
79
+ } catch (error) {
40
80
  throw new Error('Error reading or parsing file: ', error);
41
81
  }
42
82
  }
43
83
 
84
+ /**
85
+ * Extracts and parses a value from a JS object or JSON string using a JSON path expression
86
+ * @param {Object} jsonInput - A JS object or JSON string
87
+ * @param {string} path - The JSON path expression to extract the value (e.g. "$.data.id")
88
+ * @returns {*} The extracted value converted to the appropriate JavaScript type:
89
+ * - For primitive types (string, number, boolean), returns the converted value
90
+ * - For null values, returns null
91
+ * - For complex types (arrays, objects) or errors, returns null
92
+ * @example
93
+ * const json = {
94
+ * body: '{"type": "string", "value": "hello"}'
95
+ * };
96
+ * etResponseValue(response, "$.value"); // Returns "hello"
97
+ */
98
+ function getValue(jsonInput, path) {
99
+ const jsonString = safeStringify(jsonInput);
100
+ const result = getJSONValueWrapper(jsonString, path);
101
+
102
+ // 1. If result is null/undefined, return null.
103
+ if (!result || result.error !== null) {
104
+ return null;
105
+ }
106
+
107
+ try {
108
+ // 2. Parse the returned JSON string.
109
+ const parsed = JSON.parse(result.response);
110
+
111
+ // 3. If parsed is an object of the shape { type, value }, handle typed values:
112
+ // e.g., { "type": "string", "value": "hello" }
113
+ if (
114
+ parsed &&
115
+ typeof parsed === 'object' &&
116
+ !Array.isArray(parsed) &&
117
+ Object.prototype.hasOwnProperty.call(parsed, 'type') &&
118
+ Object.prototype.hasOwnProperty.call(parsed, 'value')
119
+ ) {
120
+ const { type: valueType, value } = parsed;
121
+
122
+ // Return null for complex types (arrays and objects)
123
+ if (['array', 'object', '[]interface {}'].includes(valueType)) {
124
+ return null;
125
+ }
126
+
127
+ // Convert to the appropriate JavaScript type
128
+ switch (valueType) {
129
+ case 'string':
130
+ return String(value);
131
+ case "float64", "float32", "int64", "int32", "int16", "int8", "uint64", "uint32", "uint16", "uint8":
132
+ return value;
133
+ case 'boolean':
134
+ return Boolean(value);
135
+ case 'null':
136
+ return null;
137
+ default:
138
+ // For unrecognized or fallback:
139
+ return value;
140
+ }
141
+ }
142
+
143
+ // 4. If the result is not of the form { type, value }, return it directly.
144
+ return parsed;
145
+ } catch (err) {
146
+ // 5. If JSON.parse fails or something else goes wrong, log the error and return null.
147
+ console.error(`Error processing JSONValue: ${err}`);
148
+ return null;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Validates the response body against the expected schema.
154
+ *
155
+ * @param {Object} response - An object containing a `body` property (JSON string).
156
+ * @param {Object|string} expectedSchema - The JSON schema (object or JSON string) to validate against.
157
+ * @returns {boolean} True if validation succeeds, false otherwise.
158
+ */
159
+ /**
160
+ * Validates the response body against the expected schema.
161
+ *
162
+ * @param {Object} response - The response object containing the body to validate
163
+ * @param {string} response.body - The response body as a JSON string
164
+ * @param {Object|string} expectedSchema - The JSON schema to validate against. Can be either:
165
+ * - A JSON schema object with type definitions and validation rules
166
+ * - A JSON string containing a schema definition
167
+ * @returns {boolean} True if validation succeeds, false otherwise.
168
+ */
169
+ function checkSchema(response, expectedSchema) {
170
+ const result = checkSchemaWrapper(response.responseBody, expectedSchema);
171
+ if (result.error) {
172
+ return false;
173
+ }
174
+ if (!result.response) {
175
+ return false;
176
+ }
177
+ const parsed = JSON.parse(result.response);
178
+ return parsed.result;
179
+ }
180
+
181
+ /**
182
+ * Iterate over json blob and generates list of json paths
183
+ *
184
+ * @param {Object} Dict - an object the represents json blob
185
+ * @returns {array} list of json paths
186
+ */
187
+ function iterate(fuzz_body) {
188
+ const jsonString = JSON.stringify(fuzz_body);
189
+ const result = iterateWrapper(jsonString);
190
+
191
+ if (!result) {
192
+ return []
193
+ }
194
+
195
+ const parsed = JSON.parse(result);
196
+ return parsed
197
+ }
198
+
44
199
  module.exports = {
45
- runTesterStart,
46
- readDataFromFile
200
+ createTestDescriptionFromScenario,
201
+ getYamlBytes,
202
+ readDataFromFile,
203
+ getValue,
204
+ checkSchema,
205
+ iterate,
206
+ SKYRAMP_YAML_VERSION
47
207
  }
package/src/utils.d.ts DELETED
@@ -1,5 +0,0 @@
1
- declare function runTesterStart(namespace: string, testDescription: string): Promise<void>;
2
-
3
- export {
4
- runTesterStart
5
- };