@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.
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/workers/worker-script.js +1 -1
- package/dist/cjs/workers/worker-script.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/workers/worker-script.js +1 -1
- package/dist/esm/workers/worker-script.js.map +1 -1
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -1
- package/dist/formats/yml/common/omit-headers.d.ts +3 -0
- package/dist/formats/yml/common/omit-headers.d.ts.map +1 -0
- package/dist/formats/yml/common/scripts.d.ts +3 -5
- package/dist/formats/yml/common/scripts.d.ts.map +1 -1
- package/dist/formats/yml/index.d.ts +3 -1
- package/dist/formats/yml/index.d.ts.map +1 -1
- package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
- package/dist/formats/yml/mockServerTypes.d.ts +95 -0
- package/dist/formats/yml/mockServerTypes.d.ts.map +1 -0
- package/dist/formats/yml/parseCollection.d.ts.map +1 -1
- package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/parseFolder.d.ts.map +1 -1
- package/dist/formats/yml/parseMockServer.d.ts +4 -0
- package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
- package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
- package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
- package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
- package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workers/WorkerQueue/index.d.ts +3 -2
- package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
- package/dist/workers/index.d.ts +4 -0
- package/dist/workers/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/constants.ts +5 -1
- package/src/formats/bru/tests/redact-large-text-blocks.spec.js +220 -1
- package/src/formats/bru/utils/redact-large-text-blocks.ts +46 -1
- package/src/formats/yml/common/omit-headers.spec.ts +16 -0
- package/src/formats/yml/common/omit-headers.ts +12 -0
- package/src/formats/yml/common/scripts.spec.ts +155 -0
- package/src/formats/yml/common/scripts.ts +55 -29
- package/src/formats/yml/grpc-round-trip.spec.ts +110 -0
- package/src/formats/yml/index.ts +5 -1
- package/src/formats/yml/items/parseGraphQLRequest.ts +6 -16
- package/src/formats/yml/items/parseGrpcRequest.ts +14 -6
- package/src/formats/yml/items/parseHttpRequest.ts +10 -14
- package/src/formats/yml/items/stringifyGraphQLRequest.ts +8 -14
- package/src/formats/yml/items/stringifyGrpcRequest.ts +2 -1
- package/src/formats/yml/items/stringifyHttpRequest.ts +13 -13
- package/src/formats/yml/items/stringifyWebsocketRequest.ts +3 -1
- package/src/formats/yml/mockServerTypes.ts +95 -0
- package/src/formats/yml/parseCollection.spec.ts +123 -0
- package/src/formats/yml/parseCollection.ts +23 -14
- package/src/formats/yml/parseEnvironment.spec.ts +49 -0
- package/src/formats/yml/parseEnvironment.ts +6 -0
- package/src/formats/yml/parseFolder.spec.ts +18 -0
- package/src/formats/yml/parseFolder.ts +5 -1
- package/src/formats/yml/parseMockServer.spec.ts +180 -0
- package/src/formats/yml/parseMockServer.ts +113 -0
- package/src/formats/yml/script.spec.ts +159 -0
- package/src/formats/yml/stringifyCollection.spec.ts +146 -0
- package/src/formats/yml/stringifyCollection.ts +29 -19
- package/src/formats/yml/stringifyEnvironment.spec.ts +58 -0
- package/src/formats/yml/stringifyEnvironment.ts +6 -0
- package/src/formats/yml/stringifyFolder.spec.ts +23 -0
- package/src/formats/yml/stringifyFolder.ts +10 -3
- package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
- package/src/formats/yml/stringifyMockServer.ts +129 -0
- package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
- package/src/formats/yml/tests/stringifyEnvironment.spec.ts +26 -0
- package/src/formats/yml/utils.ts +1 -1
- package/src/index.ts +34 -1
- package/src/maxRedirects.spec.ts +144 -0
- package/src/types.ts +1 -0
- package/src/workers/WorkerQueue/index.ts +10 -2
- package/src/workers/index.ts +25 -1
- 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 {
|
|
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?:
|
|
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?:
|
|
48
|
+
script?: BrunoScript;
|
|
42
49
|
tests?: string;
|
|
43
50
|
} = {};
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
brunoScripts.script[key] = code;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
for (const script of scripts) {
|
|
60
|
+
if (!script.code) {
|
|
61
|
+
continue;
|
|
57
62
|
}
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|