@usebruno/filestore 0.11.0 → 0.13.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.
Files changed (88) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/workers/worker-script.js +1 -1
  4. package/dist/cjs/workers/worker-script.js.map +1 -1
  5. package/dist/constants.d.ts +1 -1
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/esm/index.js +1 -1
  8. package/dist/esm/index.js.map +1 -1
  9. package/dist/esm/workers/worker-script.js +1 -1
  10. package/dist/esm/workers/worker-script.js.map +1 -1
  11. package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -1
  12. package/dist/formats/yml/common/omit-headers.d.ts +3 -0
  13. package/dist/formats/yml/common/omit-headers.d.ts.map +1 -0
  14. package/dist/formats/yml/common/scripts.d.ts +3 -5
  15. package/dist/formats/yml/common/scripts.d.ts.map +1 -1
  16. package/dist/formats/yml/index.d.ts +3 -1
  17. package/dist/formats/yml/index.d.ts.map +1 -1
  18. package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
  19. package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
  20. package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
  21. package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
  22. package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
  23. package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
  24. package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
  25. package/dist/formats/yml/mockServerTypes.d.ts +95 -0
  26. package/dist/formats/yml/mockServerTypes.d.ts.map +1 -0
  27. package/dist/formats/yml/parseCollection.d.ts.map +1 -1
  28. package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
  29. package/dist/formats/yml/parseFolder.d.ts.map +1 -1
  30. package/dist/formats/yml/parseMockServer.d.ts +4 -0
  31. package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
  32. package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
  33. package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
  34. package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
  35. package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
  36. package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
  37. package/dist/index.d.ts +14 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/types.d.ts +1 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/workers/WorkerQueue/index.d.ts +3 -2
  42. package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
  43. package/dist/workers/index.d.ts +4 -0
  44. package/dist/workers/index.d.ts.map +1 -1
  45. package/package.json +8 -8
  46. package/src/constants.ts +5 -1
  47. package/src/formats/bru/tests/redact-large-text-blocks.spec.js +220 -1
  48. package/src/formats/bru/utils/redact-large-text-blocks.ts +46 -1
  49. package/src/formats/yml/common/omit-headers.spec.ts +16 -0
  50. package/src/formats/yml/common/omit-headers.ts +12 -0
  51. package/src/formats/yml/common/scripts.spec.ts +155 -0
  52. package/src/formats/yml/common/scripts.ts +55 -29
  53. package/src/formats/yml/grpc-round-trip.spec.ts +110 -0
  54. package/src/formats/yml/index.ts +5 -1
  55. package/src/formats/yml/items/parseGraphQLRequest.ts +6 -16
  56. package/src/formats/yml/items/parseGrpcRequest.ts +14 -6
  57. package/src/formats/yml/items/parseHttpRequest.ts +10 -14
  58. package/src/formats/yml/items/stringifyGraphQLRequest.ts +8 -14
  59. package/src/formats/yml/items/stringifyGrpcRequest.ts +2 -1
  60. package/src/formats/yml/items/stringifyHttpRequest.ts +13 -13
  61. package/src/formats/yml/items/stringifyWebsocketRequest.ts +3 -1
  62. package/src/formats/yml/mockServerTypes.ts +95 -0
  63. package/src/formats/yml/parseCollection.spec.ts +123 -0
  64. package/src/formats/yml/parseCollection.ts +23 -14
  65. package/src/formats/yml/parseEnvironment.spec.ts +49 -0
  66. package/src/formats/yml/parseEnvironment.ts +6 -0
  67. package/src/formats/yml/parseFolder.spec.ts +18 -0
  68. package/src/formats/yml/parseFolder.ts +5 -1
  69. package/src/formats/yml/parseMockServer.spec.ts +180 -0
  70. package/src/formats/yml/parseMockServer.ts +113 -0
  71. package/src/formats/yml/script.spec.ts +159 -0
  72. package/src/formats/yml/stringifyCollection.spec.ts +146 -0
  73. package/src/formats/yml/stringifyCollection.ts +29 -19
  74. package/src/formats/yml/stringifyEnvironment.spec.ts +58 -0
  75. package/src/formats/yml/stringifyEnvironment.ts +6 -0
  76. package/src/formats/yml/stringifyFolder.spec.ts +23 -0
  77. package/src/formats/yml/stringifyFolder.ts +10 -3
  78. package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
  79. package/src/formats/yml/stringifyMockServer.ts +129 -0
  80. package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
  81. package/src/formats/yml/tests/stringifyEnvironment.spec.ts +26 -0
  82. package/src/formats/yml/utils.ts +1 -1
  83. package/src/index.ts +34 -1
  84. package/src/maxRedirects.spec.ts +144 -0
  85. package/src/types.ts +1 -0
  86. package/src/workers/WorkerQueue/index.ts +10 -2
  87. package/src/workers/index.ts +25 -1
  88. package/src/workers/worker-script.ts +45 -21
@@ -92,6 +92,53 @@ body:graphql:vars {
92
92
  }
93
93
  `;
94
94
 
95
+ const grpcBru = `meta {
96
+ name: gRPC Test
97
+ type: grpc
98
+ seq: 1
99
+ }
100
+
101
+ grpc {
102
+ url: grpc://localhost:50051
103
+ method: /hello.Greeter/SayHello
104
+ methodType: unary
105
+ }
106
+
107
+ body:grpc {
108
+ name: message 1
109
+ content: '''
110
+ {
111
+ "greeting": "hi"
112
+ }
113
+ '''
114
+ }
115
+
116
+ script:grpc:before-call-start {
117
+ const startedAt = 1;
118
+ if (startedAt) {
119
+ bru.setVar("startedAt", startedAt);
120
+ }
121
+ }
122
+
123
+ script:grpc:after-call-end {
124
+ bru.setVar("endedAt", 2);
125
+ }
126
+
127
+ script:grpc:before-message-send {
128
+ bru.setVar("sentAt", bru.grpc.request.message.timestamp);
129
+ }
130
+
131
+ script:grpc:after-message-receive {
132
+ bru.setVar("receivedAt", bru.grpc.response.message.timestamp);
133
+ }
134
+
135
+ tests {
136
+ test("hook ran", function() {
137
+ expect(bru.getVar("startedAt")).to.equal(1);
138
+ });
139
+ }
140
+ `;
141
+
95
142
  const xmlSparqlBru = `meta {
96
143
  name: Others
97
144
  type: http
@@ -169,15 +216,145 @@ body:json {
169
216
  }
170
217
  `;
171
218
 
219
+ const appBru = `meta {
220
+ name: App Request
221
+ type: http
222
+ seq: 1
223
+ }
224
+
225
+ get {
226
+ url: https://example.com
227
+ body: none
228
+ auth: none
229
+ }
230
+
231
+ app {
232
+ enabled: true
233
+ code: '''
234
+ <div id="x">hi</div>
235
+ <style>
236
+ body {
237
+ color: red;
238
+ }
239
+ </style>
240
+ <script>
241
+ function go() {
242
+ return { ok: 1 };
243
+ }
244
+ </script>
245
+ '''
246
+ }
247
+
248
+ docs {
249
+ # Docs after the app block
250
+ }
251
+ `;
252
+
253
+ const standaloneAppBru = `meta {
254
+ name: Dashboard
255
+ type: app
256
+ seq: 2
257
+ }
258
+
259
+ app {
260
+ code: '''
261
+ <div id="app">standalone</div>
262
+ '''
263
+ }
264
+ `;
265
+
266
+ const appWithoutCodeBru = `meta {
267
+ name: No Code
268
+ type: http
269
+ seq: 1
270
+ }
271
+
272
+ get {
273
+ url: https://example.com
274
+ body: none
275
+ auth: none
276
+ }
277
+
278
+ app {
279
+ enabled: true
280
+ }
281
+ `;
282
+
283
+ const appEmptyCodeBru = `meta {
284
+ name: Empty Code
285
+ type: http
286
+ seq: 1
287
+ }
288
+
289
+ app {
290
+ enabled: true
291
+ code: '''
292
+ '''
293
+ }
294
+ `;
295
+
296
+ const docsAboutAppCodeBru = `meta {
297
+ name: Docs About App Code
298
+ type: http
299
+ seq: 1
300
+ }
301
+
302
+ get {
303
+ url: https://example.com
304
+ }
305
+
306
+ docs {
307
+ # How the app block works
308
+
309
+ app {
310
+ code: '''
311
+ <div>hi</div>
312
+ '''
313
+ }
314
+ }
315
+ `;
316
+
317
+ const appWithoutCodeThenDocsBru = `meta {
318
+ name: No Code Then Docs
319
+ type: http
320
+ seq: 1
321
+ }
322
+
323
+ get {
324
+ url: https://example.com
325
+ }
326
+
327
+ app {
328
+ enabled: true
329
+ }
330
+
331
+ docs {
332
+ Set the code like this:
333
+
334
+ code: '''
335
+ <div>hi</div>
336
+ '''
337
+ }
338
+ `;
339
+
172
340
  describe('redactLargeBruTextBlocks', () => {
173
341
  describe('matches a normal parse (ohm oracle)', () => {
174
342
  const requestCases = [
175
343
  ['request with bodies, scripts, tests, docs', requestBru],
176
344
  ['graphql query + variables', graphqlBru],
177
345
  ['xml + sparql bodies', xmlSparqlBru],
346
+ ['grpc request with lifecycle hooks', grpcBru],
347
+ ['CRLF grpc request with lifecycle hooks', toCRLF(grpcBru)],
178
348
  ['CRLF request', toCRLF(requestBru)],
179
349
  ['leading + trailing blank lines in body', blankLinesBru],
180
- ['CRLF leading + trailing blank lines', toCRLF(blankLinesBru)]
350
+ ['CRLF leading + trailing blank lines', toCRLF(blankLinesBru)],
351
+ ['request-level app code', appBru],
352
+ ['CRLF request-level app code', toCRLF(appBru)],
353
+ ['standalone app item', standaloneAppBru],
354
+ ['app block without code', appWithoutCodeBru],
355
+ ['app block with an empty code value', appEmptyCodeBru],
356
+ ['docs documenting an app code block', docsAboutAppCodeBru],
357
+ ['app block without code followed by docs holding a code pair', appWithoutCodeThenDocsBru]
181
358
  ];
182
359
 
183
360
  it.each(requestCases)('%s', (_name, content) => {
@@ -205,6 +382,17 @@ describe('redactLargeBruTextBlocks', () => {
205
382
  });
206
383
  });
207
384
 
385
+ it('extracts the grpc lifecycle hooks and leaves the dictionary message block in place', () => {
386
+ const { skeleton, blocks } = redactLargeBruTextBlocks(grpcBru);
387
+ // all four lifecycle hooks + tests; `body:grpc` is a dictionary and stays in the skeleton
388
+ expect(blocks.length).toBe(5);
389
+ expect(skeleton.length).toBeLessThan(grpcBru.length);
390
+ blocks.forEach((block) => {
391
+ expect(skeleton).toContain(block.token);
392
+ expect(skeleton).not.toContain(block.value);
393
+ });
394
+ });
395
+
208
396
  it('keeps the skeleton bounded regardless of block size', () => {
209
397
  const blob = 'x'.repeat(4 * 1024 * 1024);
210
398
  const bru = `meta {
@@ -228,6 +416,37 @@ body:json {
228
416
  expect(blocks[0].value).toContain(blob);
229
417
  });
230
418
 
419
+ it('keeps huge app code out of the skeleton, pair and delimiters intact', () => {
420
+ const blob = 'x'.repeat(4 * 1024 * 1024);
421
+ const bru = appBru.replace('<div id="x">hi</div>', `<div>${blob}</div>`);
422
+
423
+ const { skeleton, blocks } = redactLargeBruTextBlocks(bru);
424
+ expect(skeleton.length).toBeLessThan(1024);
425
+ expect(skeleton).toMatch(/code: '''\n\s+__BRU_REDACTED_TEXT_BLOCK_\w+__\n\s+'''/);
426
+ expect(skeleton).toContain('enabled: true');
427
+ expect(blocks.some((block) => block.value.includes(blob))).toBe(true);
428
+ });
429
+
430
+ it('leaves an app block whose code has no value untouched', () => {
431
+ const { blocks } = redactLargeBruTextBlocks(appEmptyCodeBru);
432
+ expect(blocks).toEqual([]);
433
+ });
434
+
435
+ it('leaves a code pair outside the app block untouched', () => {
436
+ const { blocks } = redactLargeBruTextBlocks(docsAboutAppCodeBru);
437
+ expect(blocks.length).toBe(1);
438
+ expect(blocks[0].value).toContain("code: '''");
439
+ expect(blocks[0].value).toContain('<div>hi</div>');
440
+ expect(blocks[0].value).not.toContain('__BRU_REDACTED_TEXT_BLOCK_');
441
+ });
442
+
443
+ it('leaves a code pair after an app block that has no code untouched', () => {
444
+ const { blocks } = redactLargeBruTextBlocks(appWithoutCodeThenDocsBru);
445
+ expect(blocks.length).toBe(1);
446
+ expect(blocks[0].value).toContain("code: '''");
447
+ expect(blocks[0].value).not.toContain('__BRU_REDACTED_TEXT_BLOCK_');
448
+ });
449
+
231
450
  it('leaves dictionary body blocks untouched', () => {
232
451
  const multipart = `meta {
233
452
  name: Multipart
@@ -20,6 +20,10 @@ const BLOCK_TAGS = [
20
20
  'body:sparql',
21
21
  'script:pre-request',
22
22
  'script:post-response',
23
+ 'script:grpc:before-call-start',
24
+ 'script:grpc:before-message-send',
25
+ 'script:grpc:after-message-receive',
26
+ 'script:grpc:after-call-end',
23
27
  'tests',
24
28
  'docs',
25
29
  'body'
@@ -41,6 +45,47 @@ const tokenFor = (content: string): string => {
41
45
  const blockValue = (content: string[]): string =>
42
46
  outdentString(content.join('\n').replace(/^(?:\r?\n)+/, '').replace(/\r$/, ''));
43
47
 
48
+ const APP_BLOCK_OPENING = /^app[ \t]*\{\r?$/;
49
+ const MULTILINE_DELIMITER = '\'\'\'';
50
+ const APP_CODE_PAIR = `code: ${MULTILINE_DELIMITER}`;
51
+
52
+ const appCodeValue = (code: string[]): string =>
53
+ code.map((line) => line.slice(4)).join('\n').trim();
54
+
55
+ const redactAppCode = (source: string, blocks: RedactedBlock[]): string => {
56
+ const lines = source.split('\n');
57
+
58
+ const start = lines.findIndex((line) => APP_BLOCK_OPENING.test(line));
59
+ if (start === -1) {
60
+ return source;
61
+ }
62
+
63
+ const closingBrace = lines.findIndex((line, index) => index > start && isClosing(line));
64
+ const end = closingBrace === -1 ? lines.length : closingBrace;
65
+
66
+ const opening = lines.findIndex((line, index) => index > start && index < end && line.trim() === APP_CODE_PAIR);
67
+ if (opening === -1) {
68
+ return source;
69
+ }
70
+
71
+ const closing = lines.findIndex(
72
+ (line, index) => index > opening && index < end && line.trim() === MULTILINE_DELIMITER
73
+ );
74
+ if (closing === -1) {
75
+ return source;
76
+ }
77
+
78
+ const code = lines.slice(opening + 1, closing);
79
+ const value = appCodeValue(code);
80
+ if (!value.length) {
81
+ return source;
82
+ }
83
+
84
+ const token = tokenFor(code.join('\n'));
85
+ blocks.push({ token, value });
86
+ return [...lines.slice(0, opening + 1), ` ${token}`, ...lines.slice(closing)].join('\n');
87
+ };
88
+
44
89
  export const redactLargeBruTextBlocks = (content: string): RedactionResult => {
45
90
  const source = content || '';
46
91
  const skeleton: string[] = [];
@@ -49,7 +94,7 @@ export const redactLargeBruTextBlocks = (content: string): RedactionResult => {
49
94
  let openTag: string | null = null;
50
95
  let openContent: string[] = [];
51
96
 
52
- for (const line of source.split('\n')) {
97
+ for (const line of redactAppCode(source, blocks).split('\n')) {
53
98
  if (openTag === null && isOpening(line)) {
54
99
  openTag = line;
55
100
  openContent = [];
@@ -0,0 +1,16 @@
1
+ import { normalizeOmitHeaders } from './omit-headers';
2
+
3
+ describe('normalizeOmitHeaders', () => {
4
+ it('normalizes valid omitHeaders lists', () => {
5
+ expect(normalizeOmitHeaders([' User-Agent ', 'Accept-Encoding'])).toEqual(['User-Agent', 'Accept-Encoding']);
6
+ expect(normalizeOmitHeaders(['User-Agent', ''])).toEqual(['User-Agent']);
7
+ expect(normalizeOmitHeaders([null, 42, ' User-Agent '])).toEqual(['User-Agent']);
8
+ });
9
+
10
+ it('returns undefined for empty or invalid input', () => {
11
+ expect(normalizeOmitHeaders(undefined)).toBeUndefined();
12
+ expect(normalizeOmitHeaders([])).toBeUndefined();
13
+ expect(normalizeOmitHeaders(['', ' '])).toBeUndefined();
14
+ expect(normalizeOmitHeaders(null)).toBeUndefined();
15
+ });
16
+ });
@@ -0,0 +1,12 @@
1
+ /** Trim omitHeaders; return undefined when there is nothing to save. */
2
+ export const normalizeOmitHeaders = (omitHeaders: unknown): string[] | undefined => {
3
+ if (!Array.isArray(omitHeaders) || !omitHeaders.length) {
4
+ return undefined;
5
+ }
6
+
7
+ const names = omitHeaders
8
+ .map((name) => (typeof name === 'string' ? name.trim() : ''))
9
+ .filter((name) => name.length > 0);
10
+
11
+ return names.length ? names : undefined;
12
+ };
@@ -0,0 +1,155 @@
1
+ import { toOpenCollectionScripts, toBrunoScripts } from './scripts';
2
+ import { HTTP_SCRIPT_KEYS, GRPC_SCRIPT_KEYS } from '@usebruno/common';
3
+
4
+ describe('toOpenCollectionScripts', () => {
5
+ it('returns undefined when there is nothing to write', () => {
6
+ expect(toOpenCollectionScripts(null, HTTP_SCRIPT_KEYS)).toBeUndefined();
7
+ expect(toOpenCollectionScripts(undefined, HTTP_SCRIPT_KEYS)).toBeUndefined();
8
+ expect(toOpenCollectionScripts({} as any, HTTP_SCRIPT_KEYS)).toBeUndefined();
9
+ expect(toOpenCollectionScripts({ script: { req: null, res: null }, tests: null } as any, HTTP_SCRIPT_KEYS)).toBeUndefined();
10
+ });
11
+
12
+ it('writes req/res as before-request/after-response for the http keys', () => {
13
+ const out = toOpenCollectionScripts(
14
+ { script: { req: 'pre()', res: 'post()' } } as any,
15
+ HTTP_SCRIPT_KEYS
16
+ );
17
+
18
+ expect(out).toEqual([
19
+ { type: 'before-request', code: 'pre()' },
20
+ { type: 'after-response', code: 'post()' }
21
+ ]);
22
+ });
23
+
24
+ it('writes the grpc lifecycle hooks for the grpc keys', () => {
25
+ const out = toOpenCollectionScripts(
26
+ {
27
+ script: {
28
+ beforeCallStart: 'before()',
29
+ beforeMessageSend: 'beforeSend()',
30
+ afterMessageReceive: 'afterReceive()',
31
+ afterCallEnd: 'after()'
32
+ }
33
+ } as any,
34
+ GRPC_SCRIPT_KEYS
35
+ );
36
+
37
+ expect(out).toEqual([
38
+ { type: 'grpc:before-call-start', code: 'before()' },
39
+ { type: 'grpc:before-message-send', code: 'beforeSend()' },
40
+ { type: 'grpc:after-message-receive', code: 'afterReceive()' },
41
+ { type: 'grpc:after-call-end', code: 'after()' }
42
+ ]);
43
+ });
44
+
45
+ it('drops script slots outside the allowed keys', () => {
46
+ const script = {
47
+ req: 'pre()',
48
+ res: 'post()',
49
+ beforeCallStart: 'before()',
50
+ beforeMessageSend: 'beforeSend()',
51
+ afterMessageReceive: 'afterReceive()',
52
+ afterCallEnd: 'after()'
53
+ };
54
+
55
+ expect(toOpenCollectionScripts({ script } as any, HTTP_SCRIPT_KEYS)).toEqual([
56
+ { type: 'before-request', code: 'pre()' },
57
+ { type: 'after-response', code: 'post()' }
58
+ ]);
59
+ expect(toOpenCollectionScripts({ script } as any, GRPC_SCRIPT_KEYS)).toEqual([
60
+ { type: 'grpc:before-call-start', code: 'before()' },
61
+ { type: 'grpc:before-message-send', code: 'beforeSend()' },
62
+ { type: 'grpc:after-message-receive', code: 'afterReceive()' },
63
+ { type: 'grpc:after-call-end', code: 'after()' }
64
+ ]);
65
+ });
66
+
67
+ it('returns undefined when every populated slot is gated out', () => {
68
+ expect(toOpenCollectionScripts({ script: { beforeCallStart: 'before()' } } as any, HTTP_SCRIPT_KEYS)).toBeUndefined();
69
+ expect(toOpenCollectionScripts({ script: { beforeMessageSend: 'beforeSend()' } } as any, HTTP_SCRIPT_KEYS)).toBeUndefined();
70
+ expect(toOpenCollectionScripts({ script: { req: 'pre()' } } as any, GRPC_SCRIPT_KEYS)).toBeUndefined();
71
+ });
72
+
73
+ it('trims code and skips whitespace-only scripts', () => {
74
+ const out = toOpenCollectionScripts(
75
+ { script: { req: ' pre() ', res: ' \n ' }, tests: ' test() ' } as any,
76
+ HTTP_SCRIPT_KEYS
77
+ );
78
+
79
+ expect(out).toEqual([
80
+ { type: 'before-request', code: 'pre()' },
81
+ { type: 'tests', code: 'test()' }
82
+ ]);
83
+ });
84
+
85
+ it('writes tests regardless of the allowed keys', () => {
86
+ expect(toOpenCollectionScripts({ tests: 'test()' } as any, HTTP_SCRIPT_KEYS)).toEqual([
87
+ { type: 'tests', code: 'test()' }
88
+ ]);
89
+ expect(toOpenCollectionScripts({ tests: 'test()' } as any, GRPC_SCRIPT_KEYS)).toEqual([
90
+ { type: 'tests', code: 'test()' }
91
+ ]);
92
+ });
93
+ });
94
+
95
+ describe('toBrunoScripts', () => {
96
+ it('returns undefined for null / empty input', () => {
97
+ expect(toBrunoScripts(null)).toBeUndefined();
98
+ expect(toBrunoScripts(undefined)).toBeUndefined();
99
+ expect(toBrunoScripts([])).toBeUndefined();
100
+ });
101
+
102
+ it('maps every known script type to its bruno script slot', () => {
103
+ expect(
104
+ toBrunoScripts([
105
+ { type: 'before-request', code: 'pre()' },
106
+ { type: 'after-response', code: 'post()' },
107
+ { type: 'grpc:before-call-start', code: 'before()' },
108
+ { type: 'grpc:after-call-end', code: 'after()' },
109
+ { type: 'grpc:before-message-send', code: 'beforeSend()' },
110
+ { type: 'grpc:after-message-receive', code: 'afterReceive()' },
111
+ { type: 'tests', code: 'test()' }
112
+ ])
113
+ ).toEqual({
114
+ script: {
115
+ req: 'pre()',
116
+ res: 'post()',
117
+ beforeCallStart: 'before()',
118
+ afterCallEnd: 'after()',
119
+ beforeMessageSend: 'beforeSend()',
120
+ afterMessageReceive: 'afterReceive()'
121
+ },
122
+ tests: 'test()'
123
+ });
124
+ });
125
+
126
+ it('skips entries with no code and returns undefined when none remain', () => {
127
+ expect(
128
+ toBrunoScripts([
129
+ { type: 'before-request', code: '' },
130
+ { type: 'grpc:before-call-start', code: 'before()' }
131
+ ])
132
+ ).toEqual({ script: { beforeCallStart: 'before()' } });
133
+
134
+ expect(toBrunoScripts([{ type: 'before-request', code: '' }])).toBeUndefined();
135
+ });
136
+
137
+ it('ignores unrecognized script types', () => {
138
+ expect(toBrunoScripts([{ type: 'grpc:on-message', code: 'nope()' } as any])).toBeUndefined();
139
+ expect(
140
+ toBrunoScripts([
141
+ { type: 'grpc:on-message', code: 'nope()' } as any,
142
+ { type: 'before-request', code: 'pre()' }
143
+ ])
144
+ ).toEqual({ script: { req: 'pre()' } });
145
+ });
146
+
147
+ it('keeps the last script when a type repeats', () => {
148
+ expect(
149
+ toBrunoScripts([
150
+ { type: 'grpc:before-call-start', code: 'first()' },
151
+ { type: 'grpc:before-call-start', code: 'second()' }
152
+ ])
153
+ ).toEqual({ script: { beforeCallStart: 'second()' } });
154
+ });
155
+ });
@@ -1,24 +1,31 @@
1
- import type { Scripts, Script } from '@opencollection/types/common/scripts';
1
+ import type { Script as OpenCollectionScript, Scripts } from '@opencollection/types/common/scripts';
2
2
  import type { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/collection/folder';
3
3
  import type { HttpRequest as BrunoHttpRequest } from '@usebruno/schema-types/requests/http';
4
4
  import type { WebSocketRequest as BrunoWebSocketRequest } from '@usebruno/schema-types/requests/websocket';
5
5
  import type { GrpcRequest as BrunoGrpcRequest } from '@usebruno/schema-types/requests/grpc';
6
+ import type { Script as BrunoScript } from '@usebruno/schema-types/common/scripts';
6
7
 
7
- export const toOpenCollectionScripts = (request: BrunoFolderRequest | BrunoHttpRequest | BrunoWebSocketRequest | BrunoGrpcRequest | null | undefined): Scripts | undefined => {
8
+ export const toOpenCollectionScripts = (request: BrunoFolderRequest | BrunoHttpRequest | BrunoWebSocketRequest | BrunoGrpcRequest | null | undefined, allowedKeys: string[]): Scripts | undefined => {
8
9
  const ocScripts: Scripts = [];
10
+ const script = request?.script as BrunoScript | null | undefined;
11
+
12
+ const pushScript = (key: keyof BrunoScript, type: OpenCollectionScript['type'], code: string | null | undefined) => {
13
+ if (!allowedKeys.includes(key)) {
14
+ return;
15
+ }
16
+ if (!code?.trim().length) {
17
+ return;
18
+ }
19
+ ocScripts.push({ type, code: code.trim() });
20
+ };
21
+
22
+ pushScript('req', 'before-request', script?.req);
23
+ pushScript('res', 'after-response', script?.res);
24
+ pushScript('beforeCallStart', 'grpc:before-call-start', script?.beforeCallStart);
25
+ pushScript('beforeMessageSend', 'grpc:before-message-send', script?.beforeMessageSend);
26
+ pushScript('afterMessageReceive', 'grpc:after-message-receive', script?.afterMessageReceive);
27
+ pushScript('afterCallEnd', 'grpc:after-call-end', script?.afterCallEnd);
9
28
 
10
- if (request?.script?.req?.trim().length) {
11
- ocScripts.push({
12
- type: 'before-request',
13
- code: request.script.req.trim()
14
- });
15
- }
16
- if (request?.script?.res?.trim().length) {
17
- ocScripts.push({
18
- type: 'after-response',
19
- code: request.script.res.trim()
20
- });
21
- }
22
29
  if (request?.tests?.trim().length) {
23
30
  ocScripts.push({
24
31
  type: 'tests',
@@ -30,7 +37,7 @@ export const toOpenCollectionScripts = (request: BrunoFolderRequest | BrunoHttpR
30
37
  };
31
38
 
32
39
  export const toBrunoScripts = (scripts: Scripts | null | undefined): {
33
- script?: { req?: string; res?: string };
40
+ script?: BrunoScript;
34
41
  tests?: string;
35
42
  } | undefined => {
36
43
  if (!scripts || !Array.isArray(scripts) || scripts.length === 0) {
@@ -38,25 +45,44 @@ export const toBrunoScripts = (scripts: Scripts | null | undefined): {
38
45
  }
39
46
 
40
47
  const brunoScripts: {
41
- script?: { req?: string; res?: string };
48
+ script?: BrunoScript;
42
49
  tests?: string;
43
50
  } = {};
44
51
 
45
- for (const script of scripts) {
46
- if (script.type === 'before-request' && script.code) {
47
- if (!brunoScripts.script) {
48
- brunoScripts.script = {};
49
- }
50
- brunoScripts.script.req = script.code;
52
+ const setScript = (key: keyof BrunoScript, code: string) => {
53
+ if (!brunoScripts.script) {
54
+ brunoScripts.script = {};
51
55
  }
52
- if (script.type === 'after-response' && script.code) {
53
- if (!brunoScripts.script) {
54
- brunoScripts.script = {};
55
- }
56
- brunoScripts.script.res = script.code;
56
+ brunoScripts.script[key] = code;
57
+ };
58
+
59
+ for (const script of scripts) {
60
+ if (!script.code) {
61
+ continue;
57
62
  }
58
- if (script.type === 'tests' && script.code) {
59
- brunoScripts.tests = script.code;
63
+
64
+ switch (script.type) {
65
+ case 'before-request':
66
+ setScript('req', script.code);
67
+ break;
68
+ case 'after-response':
69
+ setScript('res', script.code);
70
+ break;
71
+ case 'grpc:before-call-start':
72
+ setScript('beforeCallStart', script.code);
73
+ break;
74
+ case 'grpc:before-message-send':
75
+ setScript('beforeMessageSend', script.code);
76
+ break;
77
+ case 'grpc:after-message-receive':
78
+ setScript('afterMessageReceive', script.code);
79
+ break;
80
+ case 'grpc:after-call-end':
81
+ setScript('afterCallEnd', script.code);
82
+ break;
83
+ case 'tests':
84
+ brunoScripts.tests = script.code;
85
+ break;
60
86
  }
61
87
  }
62
88