@usebruno/js 0.1.0

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.
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@usebruno/js",
3
+ "version": "0.1.0",
4
+ "main": "src/index.js",
5
+ "files": [
6
+ "src",
7
+ "package.json"
8
+ ],
9
+ "peerDependencies": {
10
+ "vm2": "^3.9.13"
11
+ },
12
+ "dependencies": {
13
+ "atob": "^2.1.2",
14
+ "ajv": "^8.12.0",
15
+ "btoa": "^1.2.1",
16
+ "crypto-js": "^4.1.1",
17
+ "json-query": "^2.2.2",
18
+ "lodash": "^4.17.21",
19
+ "moment": "^2.29.4",
20
+ "nanoid": "3.3.4",
21
+ "uuid": "^9.0.0"
22
+ }
23
+ }
package/readme.md ADDED
@@ -0,0 +1,8 @@
1
+ # bruno-js
2
+
3
+ Provides the script, test, vars and assert runtimes.
4
+
5
+ ### Publish to Npm Registry
6
+ ```bash
7
+ npm publish --access=public
8
+ ```
package/src/bru.js ADDED
@@ -0,0 +1,38 @@
1
+
2
+ class Bru {
3
+ constructor(envVariables, collectionVariables) {
4
+ this._envVariables = envVariables;
5
+ this._collectionVariables = collectionVariables;
6
+ }
7
+
8
+ getEnvVar(key) {
9
+ return this._envVariables[key];
10
+ }
11
+
12
+ setEnvVar(key, value) {
13
+ if(!key) {
14
+ throw new Error('Key is required');
15
+ }
16
+
17
+ // gracefully ignore if key is not present in environment
18
+ if(!this._envVariables.hasOwnProperty(key)) {
19
+ return;
20
+ }
21
+
22
+ this._envVariables[key] = value;
23
+ }
24
+
25
+ setVar(key, value) {
26
+ if(!key) {
27
+ throw new Error('Key is required');
28
+ }
29
+
30
+ this._collectionVariables[key] = value;
31
+ }
32
+
33
+ getVar(key) {
34
+ return this._collectionVariables[key];
35
+ }
36
+ }
37
+
38
+ module.exports = Bru;
@@ -0,0 +1,51 @@
1
+ class BrunoRequest {
2
+ constructor(req) {
3
+ this.req = req;
4
+ this.url = req.url;
5
+ this.method = req.method;
6
+ this.headers = req.headers;
7
+ this.body = req.data;
8
+ }
9
+
10
+ getUrl() {
11
+ return this.req.url;
12
+ }
13
+
14
+ setUrl(url) {
15
+ this.req.url = url;
16
+ }
17
+
18
+ getMethod() {
19
+ return this.req.method;
20
+ }
21
+
22
+ setMethod(method) {
23
+ this.req.method = method;
24
+ }
25
+
26
+ getHeaders() {
27
+ return this.req.headers;
28
+ }
29
+
30
+ setHeaders(headers) {
31
+ this.req.headers = headers;
32
+ }
33
+
34
+ getHeader(name) {
35
+ return this.req.headers[name];
36
+ }
37
+
38
+ setHeader(name, value) {
39
+ this.req.headers[name] = value;
40
+ }
41
+
42
+ getBody() {
43
+ return this.req.data;
44
+ }
45
+
46
+ setBody(data) {
47
+ this.req.data = data;
48
+ }
49
+ }
50
+
51
+ module.exports = BrunoRequest;
@@ -0,0 +1,27 @@
1
+ class BrunoResponse {
2
+ constructor(res) {
3
+ this.res = res;
4
+ this.status = res.status;
5
+ this.statusText = res.statusText;
6
+ this.headers = res.headers;
7
+ this.body = res.data;
8
+ }
9
+
10
+ getStatus() {
11
+ return this.res.status;
12
+ }
13
+
14
+ getHeader(name) {
15
+ return this.res.header[name];
16
+ }
17
+
18
+ getHeaders() {
19
+ return this.res.headers;
20
+ }
21
+
22
+ getBody() {
23
+ return this.res.data;
24
+ }
25
+ }
26
+
27
+ module.exports = BrunoResponse;
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ const ScriptRuntime = require('./runtime/script-runtime');
2
+ const TestRuntime = require('./runtime/test-runtime');
3
+ const VarsRuntime = require('./runtime/vars-runtime');
4
+ const AssertRuntime = require('./runtime/assert-runtime');
5
+
6
+ module.exports = {
7
+ ScriptRuntime,
8
+ TestRuntime,
9
+ VarsRuntime,
10
+ AssertRuntime
11
+ };
@@ -0,0 +1,226 @@
1
+ const _ = require('lodash');
2
+ const chai = require('chai');
3
+ const { nanoid } = require('nanoid');
4
+ const Bru = require('../bru');
5
+ const BrunoRequest = require('../bruno-request');
6
+ const { evaluateJsExpression, createResponseParser } = require('../utils');
7
+
8
+ const { expect } = chai;
9
+
10
+ /**
11
+ * Assertion operators
12
+ *
13
+ * eq : equal to
14
+ * neq : not equal to
15
+ * like : like
16
+ * gt : greater than
17
+ * gte : greater than or equal to
18
+ * lt : less than
19
+ * lte : less than or equal to
20
+ * in : in
21
+ * notIn : not in
22
+ * contains : contains
23
+ * notContains : not contains
24
+ * count : count
25
+ * length : length
26
+ * matches : matches
27
+ * notMatches : not matches
28
+ * startsWith : starts with
29
+ * endsWith : ends with
30
+ * between : between
31
+ * isEmpty : is empty
32
+ * isNull : is null
33
+ * isUndefined : is undefined
34
+ * isDefined : is defined
35
+ * isTruthy : is truthy
36
+ * isFalsy : is falsy
37
+ * isJson : is json
38
+ * isNumber : is number
39
+ * isString : is string
40
+ * isBoolean : is boolean
41
+ */
42
+ const parseAssertionOperator = (str = '') => {
43
+ if(!str || typeof str !== 'string' || str.length) {
44
+ return {
45
+ operator: 'eq',
46
+ value: str
47
+ };
48
+ }
49
+
50
+ const operators = [
51
+ 'eq', 'neq', 'like', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn',
52
+ 'contains', 'notContains', 'count', 'length', 'matches', 'notMatches',
53
+ 'startsWith', 'endsWith', 'between', 'isEmpty', 'isNull', 'isUndefined',
54
+ 'isDefined', 'isTruthy', 'isFalsy', 'isJson', 'isNumber', 'isString', 'isBoolean'
55
+ ];
56
+
57
+ const [operator, ...rest] = str.trim().split(' ');
58
+ const value = rest.join(' ');
59
+
60
+ if(operators.includes(operator)) {
61
+ return {
62
+ operator,
63
+ value
64
+ };
65
+ }
66
+
67
+ return {
68
+ operator: 'eq',
69
+ value: str
70
+ };
71
+ };
72
+
73
+ class AssertRuntime {
74
+ runAssertions(assertions, request, response, envVariables, collectionVariables, collectionPath) {
75
+ const enabledAssertions = _.filter(assertions, (a) => a.enabled);
76
+ if(!enabledAssertions.length) {
77
+ return [];
78
+ }
79
+
80
+ const bru = new Bru(envVariables, collectionVariables);
81
+ const req = new BrunoRequest(request);
82
+ const res = createResponseParser(response);
83
+
84
+ const bruContext = {
85
+ bru,
86
+ req,
87
+ res
88
+ };
89
+
90
+ const context = {
91
+ ...envVariables,
92
+ ...collectionVariables,
93
+ ...bruContext
94
+ }
95
+
96
+ const assertionResults = [];
97
+
98
+ // parse assertion operators
99
+ for (const v of enabledAssertions) {
100
+ const lhsExpr = v.name;
101
+ const rhsExpr = v.value;
102
+ const {
103
+ operator,
104
+ value: rhsOperand
105
+ } = parseAssertionOperator(rhsExpr);
106
+
107
+ try {
108
+ const lhs = evaluateJsExpression(lhsExpr, context);
109
+ const rhs = evaluateJsExpression(rhsOperand, context);
110
+
111
+ switch(operator) {
112
+ case 'eq':
113
+ expect(lhs).to.equal(rhs);
114
+ break;
115
+ case 'neq':
116
+ expect(lhs).to.not.equal(rhs);
117
+ break;
118
+ case 'like':
119
+ expect(lhs).to.match(new RegExp(rhs));
120
+ break;
121
+ case 'gt':
122
+ expect(lhs).to.be.greaterThan(rhs);
123
+ break;
124
+ case 'gte':
125
+ expect(lhs).to.be.greaterThanOrEqual(rhs);
126
+ break;
127
+ case 'lt':
128
+ expect(lhs).to.be.lessThan(rhs);
129
+ break;
130
+ case 'lte':
131
+ expect(lhs).to.be.lessThanOrEqual(rhs);
132
+ break;
133
+ case 'in':
134
+ expect(lhs).to.be.oneOf(rhs);
135
+ break;
136
+ case 'notIn':
137
+ expect(lhs).to.not.be.oneOf(rhs);
138
+ break;
139
+ case 'contains':
140
+ expect(lhs).to.include(rhs);
141
+ break;
142
+ case 'notContains':
143
+ expect(lhs).to.not.include(rhs);
144
+ break;
145
+ case 'count':
146
+ expect(lhs).to.have.lengthOf(rhs);
147
+ break;
148
+ case 'length':
149
+ expect(lhs).to.have.lengthOf(rhs);
150
+ break;
151
+ case 'matches':
152
+ expect(lhs).to.match(new RegExp(rhs));
153
+ break;
154
+ case 'notMatches':
155
+ expect(lhs).to.not.match(new RegExp(rhs));
156
+ break;
157
+ case 'startsWith':
158
+ expect(lhs).to.startWith(rhs);
159
+ break;
160
+ case 'endsWith':
161
+ expect(lhs).to.endWith(rhs);
162
+ break;
163
+ case 'between':
164
+ const [min, max] = value.split(' ');
165
+ expect(lhs).to.be.within(min, max);
166
+ break;
167
+ case 'isEmpty':
168
+ expect(lhs).to.be.empty;
169
+ break;
170
+ case 'isNull':
171
+ expect(lhs).to.be.null;
172
+ break;
173
+ case 'isUndefined':
174
+ expect(lhs).to.be.undefined;
175
+ break;
176
+ case 'isDefined':
177
+ expect(lhs).to.not.be.undefined;
178
+ break;
179
+ case 'isTruthy':
180
+ expect(lhs).to.be.true;
181
+ break;
182
+ case 'isFalsy':
183
+ expect(lhs).to.be.false;
184
+ break;
185
+ case 'isJson':
186
+ expect(lhs).to.be.json;
187
+ break;
188
+ case 'isNumber':
189
+ expect(lhs).to.be.a('number');
190
+ break;
191
+ case 'isString':
192
+ expect(lhs).to.be.a('string');
193
+ break;
194
+ case 'isBoolean':
195
+ expect(lhs).to.be.a('boolean');
196
+ break;
197
+ default:
198
+ expect(lhs).to.equal(rhs);
199
+ break;
200
+ }
201
+
202
+ assertionResults.push({
203
+ lhsExpr,
204
+ rhsExpr,
205
+ rhsOperand,
206
+ operator,
207
+ status: 'pass'
208
+ });
209
+ }
210
+ catch (err) {
211
+ assertionResults.push({
212
+ lhsExpr,
213
+ rhsExpr,
214
+ rhsOperand,
215
+ operator,
216
+ status: 'fail',
217
+ error: err.message
218
+ });
219
+ }
220
+ }
221
+
222
+ return assertionResults;
223
+ }
224
+ }
225
+
226
+ module.exports = AssertRuntime;
@@ -0,0 +1,93 @@
1
+ const { NodeVM } = require('vm2');
2
+ const path = require('path');
3
+ const Bru = require('../bru');
4
+ const BrunoRequest = require('../bruno-request');
5
+ const BrunoResponse = require('../bruno-response');
6
+
7
+ // Inbuilt Library Support
8
+ const atob = require('atob');
9
+ const btoa = require('btoa');
10
+ const lodash = require('lodash');
11
+ const moment = require('moment');
12
+ const uuid = require('uuid');
13
+ const nanoid = require('nanoid');
14
+ const CryptoJS = require('crypto-js');
15
+
16
+ class ScriptRuntime {
17
+ constructor() {
18
+ }
19
+
20
+ runRequestScript(script, request, environment, collectionVariables, collectionPath) {
21
+ const bru = new Bru(environment, collectionVariables);
22
+ const req = new BrunoRequest(request);
23
+
24
+ const context = {
25
+ bru,
26
+ req
27
+ };
28
+ const vm = new NodeVM({
29
+ sandbox: context,
30
+ require: {
31
+ context: 'sandbox',
32
+ external: true,
33
+ root: [collectionPath],
34
+ mock: {
35
+ atob,
36
+ btoa,
37
+ lodash,
38
+ moment,
39
+ uuid,
40
+ nanoid,
41
+ 'crypto-js': CryptoJS
42
+ }
43
+ }
44
+ });
45
+
46
+ vm.run(script, path.join(collectionPath, 'vm.js'));
47
+
48
+ return {
49
+ request,
50
+ environment,
51
+ collectionVariables
52
+ };
53
+ }
54
+
55
+ runResponseScript(script, request, response, environment, collectionVariables, collectionPath) {
56
+ const bru = new Bru(environment, collectionVariables);
57
+ const req = new BrunoRequest(request);
58
+ const res = new BrunoResponse(response);
59
+
60
+ const context = {
61
+ bru,
62
+ req,
63
+ res
64
+ };
65
+ const vm = new NodeVM({
66
+ sandbox: context,
67
+ require: {
68
+ context: 'sandbox',
69
+ external: true,
70
+ root: [collectionPath],
71
+ mock: {
72
+ atob,
73
+ btoa,
74
+ lodash,
75
+ moment,
76
+ uuid,
77
+ nanoid,
78
+ 'crypto-js': CryptoJS
79
+ }
80
+ }
81
+ });
82
+
83
+ vm.run(script, path.join(collectionPath, 'vm.js'));
84
+
85
+ return {
86
+ response,
87
+ environment,
88
+ collectionVariables
89
+ };
90
+ }
91
+ }
92
+
93
+ module.exports = ScriptRuntime;
@@ -0,0 +1,70 @@
1
+ const { NodeVM } = require('vm2');
2
+ const chai = require('chai');
3
+ const path = require('path');
4
+ const Bru = require('../bru');
5
+ const BrunoRequest = require('../bruno-request');
6
+ const BrunoResponse = require('../bruno-response');
7
+ const Test = require('../test');
8
+ const TestResults = require('../test-results');
9
+
10
+ // Inbuilt Library Support
11
+ const atob = require('atob');
12
+ const btoa = require('btoa');
13
+ const lodash = require('lodash');
14
+ const moment = require('moment');
15
+ const uuid = require('uuid');
16
+ const nanoid = require('nanoid');
17
+ const CryptoJS = require('crypto-js');
18
+
19
+ class TestRuntime {
20
+ constructor() {
21
+ }
22
+
23
+ runTests(testsFile, request, response, environment, collectionVariables, collectionPath) {
24
+ const bru = new Bru(environment, collectionVariables);
25
+ const req = new BrunoRequest(request);
26
+ const res = new BrunoResponse(response);
27
+
28
+ const __brunoTestResults = new TestResults();
29
+ const test = Test(__brunoTestResults, chai);
30
+
31
+ const context = {
32
+ test,
33
+ bru,
34
+ req,
35
+ res,
36
+ expect: chai.expect,
37
+ assert: chai.assert,
38
+ __brunoTestResults: __brunoTestResults
39
+ };
40
+
41
+ const vm = new NodeVM({
42
+ sandbox: context,
43
+ require: {
44
+ context: 'sandbox',
45
+ external: true,
46
+ root: [collectionPath],
47
+ mock: {
48
+ atob,
49
+ btoa,
50
+ lodash,
51
+ moment,
52
+ uuid,
53
+ nanoid,
54
+ 'crypto-js': CryptoJS
55
+ }
56
+ }
57
+ });
58
+
59
+ vm.run(testsFile, path.join(collectionPath, 'vm.js'));
60
+
61
+ return {
62
+ request,
63
+ environment,
64
+ collectionVariables,
65
+ results: __brunoTestResults.getResults()
66
+ };
67
+ }
68
+ }
69
+
70
+ module.exports = TestRuntime;
@@ -0,0 +1,62 @@
1
+ const _ = require('lodash');
2
+ const Bru = require('../bru');
3
+ const BrunoRequest = require('../bruno-request');
4
+ const { evaluateJsExpression, createResponseParser } = require('../utils');
5
+
6
+ class VarsRuntime {
7
+ runPreRequestVars(vars, request, envVariables, collectionVariables, collectionPath) {
8
+ const enabledVars = _.filter(vars, (v) => v.enabled);
9
+ if(!enabledVars.length) {
10
+ return;
11
+ }
12
+
13
+ const bru = new Bru(envVariables, collectionVariables);
14
+ const req = new BrunoRequest(request);
15
+
16
+ const bruContext = {
17
+ bru,
18
+ req
19
+ };
20
+
21
+ const context = {
22
+ ...envVariables,
23
+ ...collectionVariables,
24
+ ...bruContext
25
+ }
26
+
27
+ _.each(enabledVars, (v) => {
28
+ const value = evaluateJsExpression(v.value, context);
29
+ bru.setVar(v.name, value);
30
+ });
31
+ }
32
+
33
+ runPostResponseVars(vars, request, response, envVariables, collectionVariables, collectionPath) {
34
+ const enabledVars = _.filter(vars, (v) => v.enabled);
35
+ if(!enabledVars.length) {
36
+ return;
37
+ }
38
+
39
+ const bru = new Bru(envVariables, collectionVariables);
40
+ const req = new BrunoRequest(request);
41
+ const res = createResponseParser(response);
42
+
43
+ const bruContext = {
44
+ bru,
45
+ req,
46
+ res
47
+ };
48
+
49
+ const context = {
50
+ ...envVariables,
51
+ ...collectionVariables,
52
+ ...bruContext
53
+ }
54
+
55
+ _.each(enabledVars, (v) => {
56
+ const value = evaluateJsExpression(v.value, context);
57
+ bru.setVar(v.name, value);
58
+ });
59
+ }
60
+ }
61
+
62
+ module.exports = VarsRuntime;
@@ -0,0 +1,18 @@
1
+ const { nanoid } = require('nanoid');
2
+
3
+ class TestResults {
4
+ constructor() {
5
+ this.results = [];
6
+ }
7
+
8
+ addResult(result) {
9
+ result.uid = nanoid();
10
+ this.results.push(result);
11
+ }
12
+
13
+ getResults() {
14
+ return this.results;
15
+ }
16
+ }
17
+
18
+ module.exports = TestResults;
package/src/test.js ADDED
@@ -0,0 +1,27 @@
1
+ const Test = (__brunoTestResults, chai) => (description, callback) => {
2
+ try {
3
+ callback();
4
+ __brunoTestResults.addResult({ description, status: "pass" });
5
+ } catch (error) {
6
+ console.log(chai.AssertionError);
7
+ if (error instanceof chai.AssertionError) {
8
+ const { message, actual, expected } = error;
9
+ __brunoTestResults.addResult({
10
+ description,
11
+ status: "fail",
12
+ error: message,
13
+ actual,
14
+ expected
15
+ });
16
+ } else {
17
+ __brunoTestResults.addResult({
18
+ description,
19
+ status: "fail",
20
+ error: error.message || 'An unexpected error occurred.'
21
+ });
22
+ }
23
+ console.log(error);
24
+ }
25
+ }
26
+
27
+ module.exports = Test;
package/src/utils.js ADDED
@@ -0,0 +1,25 @@
1
+ const jsonQuery = require('json-query');
2
+
3
+ const evaluateJsExpression = (expression, context) => {
4
+ const fn = new Function(...Object.keys(context), `return ${expression}`);
5
+ return fn(...Object.values(context));
6
+ };
7
+
8
+ const createResponseParser = (response = {}) => {
9
+ const res = (expr) => {
10
+ const output = jsonQuery(expr, { data: response.data });
11
+ return output ? output.value : null;
12
+ }
13
+
14
+ res.status = response.status;
15
+ res.statusText = response.statusText;
16
+ res.headers = response.headers;
17
+ res.body = response.data;
18
+
19
+ return res;
20
+ };
21
+
22
+ module.exports = {
23
+ evaluateJsExpression,
24
+ createResponseParser
25
+ };