@usebruno/js 0.51.0 → 0.52.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.
@@ -11,7 +11,8 @@ const {
11
11
  parseErrorLocation,
12
12
  getSourceContextFromContent,
13
13
  adjustStackTrace,
14
- buildStackFromCallSites
14
+ buildStackFromCallSites,
15
+ posixifyPath
15
16
  } = require('./error-formatter');
16
17
  const fs = require('fs');
17
18
  const path = require('path');
@@ -120,6 +121,64 @@ const COLLECTION_YML = [
120
121
  ' });'
121
122
  ].join('\n');
122
123
 
124
+ // gRPC lifecycle hooks:
125
+ // 11: script:grpc:before-call-start { → blockStartLine = 12
126
+ // 16: script:grpc:after-call-end { → blockStartLine = 17
127
+ const GRPC_BRU = `meta {
128
+ name: grpc-test
129
+ type: grpc
130
+ seq: 1
131
+ }
132
+
133
+ grpc {
134
+ url: grpc://localhost:50051
135
+ }
136
+
137
+ script:grpc:before-call-start {
138
+ const token = bru.getEnvVar('token');
139
+ request.setHeader('authorization', token);
140
+ }
141
+
142
+ script:grpc:after-call-end {
143
+ const data = response.data;
144
+ bru.setVar('userId', data.id);
145
+ console.log(data);
146
+ }
147
+
148
+ script:grpc:before-message-send {
149
+ bru.setVar('outbound', bru.grpc.request.message.data.greeting);
150
+ }
151
+
152
+ script:grpc:after-message-receive {
153
+ const message = bru.grpc.response.message;
154
+ bru.setVar('inbound', message.data.reply);
155
+ }`;
156
+
157
+ // gRPC yml fixture: blockStartLine = 8 (before-call-start), 12 (after-call-end),
158
+ // 16 (before-message-send), 19 (after-message-receive)
159
+ const GRPC_YML = [
160
+ 'info:',
161
+ ' name: grpc-yaml-test',
162
+ ' version: "1"',
163
+ 'runtime:',
164
+ ' scripts:',
165
+ ' - type: grpc:before-call-start',
166
+ ' code: |-',
167
+ ' const token = bru.getEnvVar(\'token\');',
168
+ ' request.setHeader(\'authorization\', token);',
169
+ ' - type: grpc:after-call-end',
170
+ ' code: |-',
171
+ ' const data = response.data;',
172
+ ' bru.setVar(\'userId\', data.id);',
173
+ ' - type: grpc:before-message-send',
174
+ ' code: |-',
175
+ ' bru.setVar(\'outbound\', bru.grpc.request.message.data.greeting);',
176
+ ' - type: grpc:after-message-receive',
177
+ ' code: |-',
178
+ ' const message = bru.grpc.response.message;',
179
+ ' bru.setVar(\'inbound\', message.data.reply);'
180
+ ].join('\n');
181
+
123
182
  // Wrapper offsets: QuickJS = 9 (script line 1 = VM line 10), NodeVM = 2 (script line 1 = VM line 3)
124
183
 
125
184
  describe('Error Formatter', () => {
@@ -128,6 +187,8 @@ describe('Error Formatter', () => {
128
187
  let ymlFilePath;
129
188
  let bruWithCommentsPath;
130
189
  let collectionYmlPath;
190
+ let grpcBruPath;
191
+ let grpcYmlPath;
131
192
 
132
193
  beforeEach(() => {
133
194
  testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bruno-test-'));
@@ -135,10 +196,14 @@ describe('Error Formatter', () => {
135
196
  ymlFilePath = path.join(testDir, 'test.yml');
136
197
  bruWithCommentsPath = path.join(testDir, 'comments.bru');
137
198
  collectionYmlPath = path.join(testDir, 'opencollection.yml');
199
+ grpcBruPath = path.join(testDir, 'grpc.bru');
200
+ grpcYmlPath = path.join(testDir, 'grpc.yml');
138
201
  fs.writeFileSync(bruFilePath, MULTI_BLOCK_BRU);
139
202
  fs.writeFileSync(ymlFilePath, MULTI_BLOCK_YML);
140
203
  fs.writeFileSync(bruWithCommentsPath, BRU_WITH_COMMENTS);
141
204
  fs.writeFileSync(collectionYmlPath, COLLECTION_YML);
205
+ fs.writeFileSync(grpcBruPath, GRPC_BRU);
206
+ fs.writeFileSync(grpcYmlPath, GRPC_YML);
142
207
  });
143
208
 
144
209
  afterEach(() => {
@@ -152,6 +217,13 @@ describe('Error Formatter', () => {
152
217
  expect(findScriptBlockStartLine(bruFilePath, 'test')).toBe(25);
153
218
  });
154
219
 
220
+ it('should find gRPC lifecycle hook blocks in .bru files', () => {
221
+ expect(findScriptBlockStartLine(grpcBruPath, 'before-call-start')).toBe(12);
222
+ expect(findScriptBlockStartLine(grpcBruPath, 'after-call-end')).toBe(17);
223
+ expect(findScriptBlockStartLine(grpcBruPath, 'before-message-send')).toBe(23);
224
+ expect(findScriptBlockStartLine(grpcBruPath, 'after-message-receive')).toBe(27);
225
+ });
226
+
155
227
  it('should return null for missing block or non-.bru files', () => {
156
228
  const noBlockPath = path.join(testDir, 'no-block.bru');
157
229
  fs.writeFileSync(noBlockPath, 'meta {\n name: test\n}');
@@ -167,6 +239,13 @@ describe('Error Formatter', () => {
167
239
  expect(findScriptBlockEndLine(bruFilePath, 'test')).toBe(30);
168
240
  });
169
241
 
242
+ it('should find last content line for gRPC lifecycle hook blocks', () => {
243
+ expect(findScriptBlockEndLine(grpcBruPath, 'before-call-start')).toBe(13);
244
+ expect(findScriptBlockEndLine(grpcBruPath, 'after-call-end')).toBe(19);
245
+ expect(findScriptBlockEndLine(grpcBruPath, 'before-message-send')).toBe(23);
246
+ expect(findScriptBlockEndLine(grpcBruPath, 'after-message-receive')).toBe(28);
247
+ });
248
+
170
249
  it('should return null for empty block', () => {
171
250
  const emptyBlockPath = path.join(testDir, 'empty.bru');
172
251
  fs.writeFileSync(emptyBlockPath, 'script:pre-request {\n}');
@@ -197,6 +276,13 @@ describe('Error Formatter', () => {
197
276
  expect(findYmlScriptBlockStartLine(collectionYmlPath, 'test')).toBe(11);
198
277
  });
199
278
 
279
+ it('should find gRPC lifecycle hook blocks in .yml files', () => {
280
+ expect(findYmlScriptBlockStartLine(grpcYmlPath, 'before-call-start')).toBe(8);
281
+ expect(findYmlScriptBlockStartLine(grpcYmlPath, 'after-call-end')).toBe(12);
282
+ expect(findYmlScriptBlockStartLine(grpcYmlPath, 'before-message-send')).toBe(16);
283
+ expect(findYmlScriptBlockStartLine(grpcYmlPath, 'after-message-receive')).toBe(19);
284
+ });
285
+
200
286
  it('should return null for missing block or non-.yml files', () => {
201
287
  const noRuntimePath = path.join(testDir, 'no-runtime.yml');
202
288
  fs.writeFileSync(noRuntimePath, 'info:\n name: simple\n version: "1"\n');
@@ -216,6 +302,13 @@ describe('Error Formatter', () => {
216
302
  expect(findYmlScriptBlockEndLine(collectionYmlPath, 'test')).toBe(13);
217
303
  });
218
304
 
305
+ it('should find last content line for gRPC lifecycle hook blocks', () => {
306
+ expect(findYmlScriptBlockEndLine(grpcYmlPath, 'before-call-start')).toBe(9);
307
+ expect(findYmlScriptBlockEndLine(grpcYmlPath, 'after-call-end')).toBe(13);
308
+ expect(findYmlScriptBlockEndLine(grpcYmlPath, 'before-message-send')).toBe(16);
309
+ expect(findYmlScriptBlockEndLine(grpcYmlPath, 'after-message-receive')).toBe(20);
310
+ });
311
+
219
312
  it('should return null for missing block', () => {
220
313
  const noRuntimePath = path.join(testDir, 'no-runtime.yml');
221
314
  fs.writeFileSync(noRuntimePath, 'info:\n name: simple\n version: "1"\n');
@@ -804,7 +897,9 @@ get {
804
897
  const result = formatErrorWithContextV2(error, 'pre-request');
805
898
 
806
899
  expect(result).not.toBeNull();
807
- expect(result.filePath).toBe(bruFilePath);
900
+ // display paths are posixified, so the Windows path.join backslashes
901
+ // must be normalized before comparing
902
+ expect(result.filePath).toBe(posixifyPath(bruFilePath));
808
903
  });
809
904
  });
810
905
 
@@ -1,7 +1,6 @@
1
1
  const TestResults = require('../test-results');
2
2
  const Test = require('../test');
3
3
 
4
- // Calculate summary statistics for test results
5
4
  const getResultsSummary = (results) => {
6
5
  const summary = {
7
6
  total: results.length,
@@ -20,12 +19,40 @@ const getResultsSummary = (results) => {
20
19
  return summary;
21
20
  };
22
21
 
22
+ /**
23
+ * Called once per script phase - pre-request, post-response, and the Tests tab each call
24
+ * this separately, from their own place in runtime/script-runtime.js and
25
+ * runtime/test-runtime.js. Every call creates its own private `pendingTestPromises` array
26
+ * below, so each phase tracks and waits for only its own test() calls
27
+ */
23
28
  const createBruTestResultMethods = (bru, assertionResults, chai) => {
24
29
  const __brunoTestResults = new TestResults();
25
- const test = Test(__brunoTestResults, chai);
30
+ const baseTest = Test(__brunoTestResults, chai);
31
+
32
+ const pendingTestPromises = [];
33
+
34
+ const test = (description, callback) => {
35
+ const promise = baseTest(description, callback);
36
+ pendingTestPromises.push(promise.catch(() => {}));
37
+ return promise;
38
+ };
39
+
40
+ /**
41
+ * Waits for every test() call registered so far to settle - including a test() called
42
+ * from inside another test()'s callback, after this wait has already started. Mirrors
43
+ * QuickJS's own waitForPendingDeferreds(). If a test() callback has its own delay (a
44
+ * setTimeout, a slow request), this simply waits until that delay is over
45
+ */
46
+ const waitForPendingTests = async () => {
47
+ while (pendingTestPromises.length) {
48
+ const batch = pendingTestPromises.splice(0);
49
+ await Promise.all(batch);
50
+ }
51
+ };
52
+
26
53
  setupBruTestMethods(bru, __brunoTestResults, assertionResults);
27
54
 
28
- return { __brunoTestResults, test };
55
+ return { __brunoTestResults, test, waitForPendingTests };
29
56
  };
30
57
 
31
58
  const setupBruTestMethods = (bru, __brunoTestResults, assertionResults) => {