@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Skyramp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,24 +1,17 @@
1
- # Skyramp
2
- Skyramp is an npm module that provides utility functions for leveraging [skyramp CLI](https://skyramp.dev/docs/commands/skyramp/) commands.
1
+ # skyramp
2
+
3
+ `skyramp` is an npm module that provides functionalities to generate and execute a variety of functional tests (including smoke, contract, and integration tests). It also offers features for asserting scenarios in various test environments.
4
+
5
+ For more information about Skyramp, please visit [our website](https://skyramp.dev).
3
6
 
4
7
  ## Installation
5
- To install Skyramp, simply run the following command in your terminal:
6
- ```
7
- npm install skyramp
8
- ```
8
+ To install Skyramp, please refer to the [installation instructions](https://skyramp.dev/docs/quickstart/install) to set up the Skyramp CLI as well as the required dependencies.
9
9
 
10
10
  ## Usage
11
- Once you've installed Skyramp, you can import it into your project like this:
11
+ To use `skyramp` please refer to the guide to [generate your first test](https:skyramp.dev/docs/quickstart/first-test-general). Once you generate your first integration test in TypeScript using the Skyramp CLI, you can execute the test with the following command:
12
12
 
13
+ ```bash
14
+ npx playwright test products_integration_test.spec.ts --reporter=list
13
15
  ```
14
- const skyramp = require('skyramp');
15
- ```
16
16
 
17
- Skyramp currently exports the following six functions:
18
- - `runConfigApplyLocal`: create a new local test cluster
19
- - `runAddKubeconfig`: specify kubeconfig for a pre-provisioned workload cluster
20
- - `runConfigRemoveLocal`: remove a local test cluster
21
- - `runConfigRemove`: remove a test cluster
22
- - `runMockerCreate`: generate Mocker configurations
23
- - `runMockerDeploy`: update Mocker configurations and source schema files
24
- - `runTesterStart`: starts a skyramp test specified in the test description file
17
+
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@skyramp/skyramp",
3
- "version": "1.0.0-sha.b2dfe11",
3
+ "version": "1.2.2",
4
4
  "description": "module for leveraging skyramp cli functionality",
5
5
  "scripts": {
6
+ "lint": "eslint 'src/**/*.js' 'src/**/*.ts' --fix",
6
7
  "pack": "npm pack",
7
- "publish-npm": "npm publish --access private --ignore-scripts --@skyramp:skyramp='https://registry.npmjs.org'"
8
+ "reference-docs": "documentation build src/**/*.js src/classes/**/*.js -f md -o reference.md",
9
+ "postinstall": "node scripts/download-binary.js",
10
+ "clean": "rimraf node_modules && rimraf package-lock.json && rimraf skyramp && rimraf lib/*"
8
11
  },
9
12
  "files": [
10
- "lib/**",
13
+ "scripts/*.js",
11
14
  "src/*.js",
12
15
  "src/*.ts",
13
16
  "src/classes/*.js",
@@ -15,10 +18,16 @@
15
18
  ],
16
19
  "main": "src/index.js",
17
20
  "author": "",
18
- "license": "ISC",
21
+ "license": "MIT",
19
22
  "dependencies": {
23
+ "@aws-sdk/client-s3": "^3.812.0",
20
24
  "fs": "^0.0.1-security",
21
25
  "js-yaml": "^4.1.0",
22
- "koffi": "^2.3.20"
26
+ "koffi": "2.5.12"
27
+ },
28
+ "devDependencies": {
29
+ "@typescript-eslint/eslint-plugin": "^6.14.0",
30
+ "@typescript-eslint/parser": "^6.14.0",
31
+ "eslint": "^8.55.0"
23
32
  }
24
33
  }
@@ -0,0 +1,189 @@
1
+ const https = require('https');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+ const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
6
+
7
+ function log(level, message) {
8
+ const timestamp = new Date().toISOString();
9
+ console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
10
+ }
11
+
12
+ let S3_PRIVATE = process.env.S3_PRIVATE || false; // Set to true if libraries are in a private S3 bucket
13
+ const AWS_REGION = process.env.AWS_REGION || 'us-west-2'; // Set your AWS region
14
+ const SKIP_DOWNLOAD = process.env.SKIP_DOWNLOAD || false; // Set to true to skip the download process
15
+ const PUBLIC_BUCKET_NAME = process.env.INT_LIBRARY_BUCKET || 'skyramp-public'; // Set to your S3 public bucket name
16
+ const PUBLIC_LIBRARY_PATH = process.env.INT_LIBRARY_PATH || `release/v${require('../package.json').version}/lib`; // Set to your S3 public library root path
17
+ const PRIVATE_BUCKET_NAME = process.env.INT_LIBRARY_BUCKET || undefined; // Set to your S3 private bucket name
18
+ const PRIVATE_LIBRARY_PATH = process.env.INT_LIBRARY_PATH || ''; // Set to your S3 private library root path
19
+
20
+ if (SKIP_DOWNLOAD) {
21
+ log('info', "Skipping download as requested by SKIP_DOWNLOAD=true.");
22
+ process.exit(0);
23
+ }
24
+
25
+ if (process.env.CI) {
26
+ log('info', "Running in CI environment. Defaulting to private S3 download.");
27
+ S3_PRIVATE = true;
28
+ }
29
+
30
+ const archMap = {
31
+ x64: 'amd64',
32
+ ia32: '386',
33
+ arm: 'arm',
34
+ arm64: 'arm64',
35
+ };
36
+
37
+ const platformMap = {
38
+ win32: 'windows',
39
+ darwin: 'darwin',
40
+ linux: 'linux',
41
+ };
42
+
43
+ const extMap = {
44
+ windows: 'dll',
45
+ darwin: 'dylib',
46
+ linux: 'so',
47
+ };
48
+
49
+ const rawPlatform = process.platform;
50
+ const rawArch = process.arch;
51
+ const platform = platformMap[rawPlatform];
52
+ const arch = archMap[rawArch];
53
+ const ext = extMap[platform];
54
+
55
+ if (!platform || !arch || !ext) {
56
+ log('error', `Unsupported platform or architecture: ${rawPlatform} / ${rawArch}`);
57
+ process.exit(1);
58
+ }
59
+
60
+ const prefix = 'skyramp';
61
+ const binaryFilename = `${prefix}-${platform}-${arch}.${ext}`;
62
+ const headerFilename = `${prefix}-${platform}-${arch}.h`;
63
+
64
+ const baseUrl = `https://${PUBLIC_BUCKET_NAME}.s3.${AWS_REGION}.amazonaws.com/${PUBLIC_LIBRARY_PATH}/`;
65
+ const localDir = path.join(__dirname, '..', 'lib');
66
+
67
+ const files = [
68
+ { name: binaryFilename, dest: path.join(localDir, binaryFilename) },
69
+ { name: headerFilename, dest: path.join(localDir, headerFilename) },
70
+ ];
71
+
72
+ async function calculateMD5(filePath) {
73
+ return new Promise((resolve, reject) => {
74
+ const hash = crypto.createHash('md5');
75
+ const stream = fs.createReadStream(filePath);
76
+ stream.on('data', data => hash.update(data));
77
+ stream.on('end', () => resolve(hash.digest('hex')));
78
+ stream.on('error', (err) => {
79
+ log('error', `Failed to calculate MD5 for ${filePath}: ${err.message}`);
80
+ reject(err);
81
+ });
82
+ });
83
+ }
84
+
85
+ async function getETag(url) {
86
+ return new Promise((resolve, reject) => {
87
+ https.get(url, { method: 'HEAD' }, res => {
88
+ if (res.statusCode !== 200) {
89
+ reject(new Error(`HTTP ${res.statusCode}: ${url}`));
90
+ return;
91
+ }
92
+ resolve(res.headers['etag']?.replace(/"/g, '')); // Remove quotes from ETag
93
+ }).on('error', (err) => {
94
+ log('error', `Error during HEAD request to ${url}: ${err.message}`);
95
+ reject(err);
96
+ });
97
+ });
98
+ }
99
+
100
+ async function download(url, dest, options = {}) {
101
+ await fs.promises.mkdir(path.dirname(dest), { recursive: true });
102
+
103
+ const { session, bucket, s3Key } = options;
104
+
105
+ return new Promise((resolve, reject) => {
106
+ if (session && bucket && s3Key) {
107
+ // Private S3 download using AWS SDK
108
+ const command = new GetObjectCommand({ Bucket: bucket, Key: s3Key });
109
+ session.send(command).then(response => {
110
+ const stream = response.Body;
111
+ const file = fs.createWriteStream(dest);
112
+ stream.pipe(file);
113
+ stream.on('error', (err) => {
114
+ log('error', `Error streaming file from S3: ${err.message}`);
115
+ reject(err);
116
+ });
117
+ file.on('finish', () => {
118
+ log('info', `Successfully downloaded ${s3Key} to ${dest}`);
119
+ file.close(resolve);
120
+ });
121
+ }).catch(err => {
122
+ log('error', `Failed to download ${s3Key} from S3: ${err.message}`);
123
+ reject(err);
124
+ });
125
+ } else {
126
+ // Public URL download using https
127
+ https.get(url, res => {
128
+ if (res.statusCode !== 200) {
129
+ const error = new Error(`HTTP ${res.statusCode}: ${url}`);
130
+ log('error', `Failed to download ${url}: ${error.message}`);
131
+ reject(error);
132
+ return;
133
+ }
134
+ const file = fs.createWriteStream(dest);
135
+ res.pipe(file);
136
+ file.on('finish', () => {
137
+ log('info', `Successfully downloaded ${url} to ${dest}`);
138
+ file.close(resolve);
139
+ });
140
+ }).on('error', (err) => {
141
+ log('error', `Error during download from ${url}: ${err.message}`);
142
+ reject(err);
143
+ });
144
+ }
145
+ });
146
+ }
147
+
148
+ (async () => {
149
+ for (const file of files) {
150
+ let options = {};
151
+ let url = baseUrl + file.name;
152
+ const localFile = file.dest;
153
+ log('info', `Processing ${file.name}...`);
154
+
155
+ try {
156
+ if (S3_PRIVATE) {
157
+ log("Pulling libraries from Private S3.");
158
+ const s3 = new S3Client({region: AWS_REGION});
159
+
160
+ url = null; // No URL needed for S3 private download client
161
+ options = {
162
+ session: s3,
163
+ bucket: PRIVATE_BUCKET_NAME,
164
+ s3Key: `${PRIVATE_LIBRARY_PATH}/${file.name}`
165
+ };
166
+ } else if (fs.existsSync(localFile)) {
167
+ log('info', `Checking ${file.name} for changes...`);
168
+ const remoteETag = await getETag(url);
169
+ const localHash = await calculateMD5(localFile);
170
+ log('debug', `Remote ETag: ${remoteETag}, Local Hash: ${localHash}`);
171
+
172
+ if (remoteETag === localHash) {
173
+ log('info', `✅ ${file.name} is up-to-date.`);
174
+ continue;
175
+ } else {
176
+ log('info', `🔄 ${file.name} differs, downloading...`);
177
+ }
178
+ } else {
179
+ log('info', `⬇️ ${file.name} not found locally, downloading...`);
180
+ }
181
+
182
+ await download(url, file.dest, options);
183
+ log('info', `✅ Saved ${file.name} to ${file.dest}`);
184
+ } catch (e) {
185
+ log('error', `❌ Failed to process ${file.name}: ${e.message}`);
186
+ process.exit(1);
187
+ }
188
+ }
189
+ })();
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @typedef {Object} AssertOptions
3
+ * @property {string} assertValue - The value to be asserted.
4
+ * @property {string} expectedValue - The expected value for the assertion.
5
+ * @property {string} assertStepName - The name of the step.
6
+ * @property {string} description - The description of the assertion.
7
+ */
8
+ interface AssertOptions {
9
+ assertValue: string;
10
+ expectedValue: string;
11
+ assertStepName: string;
12
+ description: string;
13
+ }
14
+ export declare class Asserts {
15
+ constructor(options: AssertOptions);
16
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The `Assert` class represents an assertion to be used in a test step.
3
+ * @class
4
+ */
5
+ class Assert {
6
+ /**
7
+ * Initialize a new instance of an Assert.
8
+ * @constructor
9
+ * @param {Object} option - The options for initializing the Assert object.
10
+ * @param {*} option.assertValue - The value to be asserted.
11
+ * @param {*} option.assertExpectedValue - The expected value for the assertion.
12
+ * @param {string} option.stepName - The name of the step.
13
+ * @param {string} option.description - The description of the assertion.
14
+ */
15
+ constructor(option) {
16
+ this.assertValue = option.assertValue;
17
+ this.expectedValue = option.assertExpectedValue;
18
+ this.name = option.stepName;
19
+ this.description = option.description;
20
+ }
21
+
22
+ /**
23
+ * Convert the assertion to a JSON object.
24
+ * @returns {Object} The JSON representation of the assertion.
25
+ */
26
+ toJson() {
27
+ return {
28
+ asserts: `${this.assertValue} == ${this.expectedValue}`,
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Convert the assertion to a string representation.
34
+ * @returns {string} The string representation of the assertion.
35
+ */
36
+ toAssert() {
37
+ return `${this.assertValue} == ${this.expectedValue}`
38
+ }
39
+ }
40
+
41
+ module.exports = Assert;
@@ -0,0 +1,133 @@
1
+ import { RequestV2 } from "./RequestV2";
2
+
3
+ export interface AsyncRequestOptions {
4
+ name?: string;
5
+ url?: string;
6
+ path?: string;
7
+ method?: string;
8
+ body?: string;
9
+ headers?: Record<string, string>;
10
+ pathParams?: Record<string, string>;
11
+ queryParams?: Record<string, string | number | boolean>;
12
+ formParams?: Record<string, string | number | boolean>;
13
+ multipartParams?: Record<string, string | number | boolean>;
14
+ dataOverride?: Record<string, string | number | boolean>;
15
+ description?: string;
16
+ expectedCode?: string;
17
+ if_?: string;
18
+ until?: string;
19
+ maxRetries?: number;
20
+ retryInterval?: number;
21
+ }
22
+
23
+ export class AsyncRequest {
24
+ scenario: AsyncScenario;
25
+ request: RequestV2;
26
+ stepIndex: number;
27
+
28
+ constructor(scenario: AsyncScenario, request: RequestV2, stepIndex: number);
29
+
30
+ /**
31
+ * Returns request's response value construct for asynchronous backend
32
+ * @param path The path to the value
33
+ */
34
+ getAsyncRequestValue(path: string | null): string;
35
+
36
+ /**
37
+ * Returns request's status check construct for asynchronous backend
38
+ * @param code The expected status code
39
+ */
40
+ requestStatusCheck(code: string | number): string;
41
+
42
+ /**
43
+ * Adds an assert to the request
44
+ * @param jsonPath The JSON path to the value
45
+ * @param expectedValue The expected value
46
+ */
47
+ assertEqual(jsonPath: string, expectedValue: string | number | boolean): void;
48
+
49
+ /**
50
+ * Adds a not equal assert to the request
51
+ * @param jsonPath The JSON path to the value
52
+ * @param value The value to compare against
53
+ */
54
+ assertNotEqual(jsonPath: string, value: string | number | boolean): void;
55
+ }
56
+
57
+ export interface AsyncScenarioOptions {
58
+ name: string;
59
+ ignoreFailure?: boolean;
60
+ }
61
+
62
+ export class AsyncScenario {
63
+ name: string;
64
+ steps: AsyncRequest[];
65
+ vars: Record<string, string | number | boolean>;
66
+ until: string;
67
+ maxRetries: number;
68
+ retryInterval: number;
69
+ ignoreFailure: boolean;
70
+
71
+ /**
72
+ * Creates a new AsyncScenario
73
+ * @param options The scenario options
74
+ */
75
+ constructor(options: AsyncScenarioOptions);
76
+
77
+ /**
78
+ * Adds an asynchronous request to this scenario
79
+ * @param options The request options
80
+ */
81
+ addAsyncRequest(options?: AsyncRequestOptions): AsyncRequest;
82
+
83
+ /**
84
+ * Adds an asynchronous nested scenario to this scenario
85
+ * @param nestedScenario The nested scenario to add
86
+ * @param until The condition to stop retrying
87
+ * @param maxRetries The maximum number of retries
88
+ * @param retryInterval The interval between retries
89
+ */
90
+ addAsyncScenario(
91
+ nestedScenario: AsyncScenario,
92
+ until?: string,
93
+ maxRetries?: number,
94
+ retryInterval?: number
95
+ ): void;
96
+
97
+ /**
98
+ * Sets a scenario level variable
99
+ * @param varName The name of the variable
100
+ * @param value The value of the variable
101
+ */
102
+ setAsyncVar(varName: string, value: string | number | boolean): void;
103
+
104
+ /**
105
+ * Returns scenario's variable construct for asynchronous backend
106
+ * @param varName The name of the variable
107
+ */
108
+ getAsyncVar(varName: string): string;
109
+
110
+ /**
111
+ * Returns scenario's response value construct for asynchronous backend
112
+ * @param varName The name of the variable
113
+ */
114
+ getAsyncScenarioValue(varName: string): string;
115
+
116
+ /**
117
+ * Sets a scenario level variable for export
118
+ * @param varName The name of the variable
119
+ * @param value The value of the variable
120
+ */
121
+ exportAsyncVar(varName: string, value: string | number | boolean): void;
122
+
123
+ /**
124
+ * Adds an assert to the scenario
125
+ * @param value The value to assert
126
+ */
127
+ addAssert(value: string): void;
128
+
129
+ /**
130
+ * Converts this scenario to JSON
131
+ */
132
+ toJson(): Record<string, string | number | boolean | object | null>;
133
+ }