@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
@@ -0,0 +1,110 @@
1
+ import type { GrpcRequest as BrunoGrpcRequest } from '@usebruno/schema-types/requests/grpc';
2
+ import stringifyItem from './stringifyItem';
3
+ import parseItem from './parseItem';
4
+
5
+ const parseGrpcItem = (yml: string) => parseItem(yml).request as BrunoGrpcRequest;
6
+
7
+ const grpcItem = (script: Record<string, string | null>, tests: string | null = null) => ({
8
+ uid: 'i1',
9
+ type: 'grpc-request',
10
+ name: 'say hello',
11
+ seq: 1,
12
+ request: {
13
+ url: 'grpc://localhost:50051',
14
+ method: '/hello.Greeter/SayHello',
15
+ methodType: 'unary',
16
+ headers: [],
17
+ auth: { mode: 'none' },
18
+ body: { mode: 'grpc', grpc: [{ name: 'message 1', content: '{}' }] },
19
+ script,
20
+ tests,
21
+ vars: { req: [], res: [] },
22
+ assertions: []
23
+ }
24
+ }) as any;
25
+
26
+ describe('grpc lifecycle hooks — stringify/parse round trip', () => {
27
+ it('writes every hook as a grpc-prefixed script type and reads them back', () => {
28
+ const yml = stringifyItem(grpcItem({
29
+ beforeCallStart: 'bru.setVar("a", 1);',
30
+ afterCallEnd: 'bru.setVar("b", 2);',
31
+ beforeMessageSend: 'bru.setVar("c", 3);',
32
+ afterMessageReceive: 'bru.setVar("d", 4);'
33
+ }));
34
+
35
+ expect(yml).toContain('grpc:before-call-start');
36
+ expect(yml).toContain('grpc:after-call-end');
37
+ expect(yml).toContain('grpc:before-message-send');
38
+ expect(yml).toContain('grpc:after-message-receive');
39
+
40
+ const { script } = parseGrpcItem(yml);
41
+ expect(script!.beforeCallStart).toBe('bru.setVar("a", 1);');
42
+ expect(script!.afterCallEnd).toBe('bru.setVar("b", 2);');
43
+ expect(script!.beforeMessageSend).toBe('bru.setVar("c", 3);');
44
+ expect(script!.afterMessageReceive).toBe('bru.setVar("d", 4);');
45
+ });
46
+
47
+ it('preserves a multiline hook verbatim', () => {
48
+ const beforeCallStart = ['const now = 1;', 'if (now) {', ' bru.setVar("startedAt", now);', '}'].join('\n');
49
+
50
+ const yml = stringifyItem(grpcItem({ beforeCallStart, afterCallEnd: null }));
51
+ const { script } = parseGrpcItem(yml);
52
+
53
+ expect(script!.beforeCallStart).toBe(beforeCallStart);
54
+ });
55
+
56
+ it('round-trips one hook without inventing the others', () => {
57
+ const yml = stringifyItem(grpcItem({ beforeCallStart: null, afterCallEnd: 'bru.setVar("b", 2);' }));
58
+
59
+ expect(yml).not.toContain('grpc:before-call-start');
60
+ expect(yml).not.toContain('grpc:before-message-send');
61
+ expect(yml).not.toContain('grpc:after-message-receive');
62
+
63
+ const { script } = parseGrpcItem(yml);
64
+ expect(script!.beforeCallStart).toBeNull();
65
+ expect(script!.afterCallEnd).toBe('bru.setVar("b", 2);');
66
+ expect(script!.beforeMessageSend).toBeNull();
67
+ expect(script!.afterMessageReceive).toBeNull();
68
+ });
69
+
70
+ it('writes no scripts block when every hook is empty', () => {
71
+ const yml = stringifyItem(grpcItem({
72
+ beforeCallStart: null,
73
+ afterCallEnd: ' ',
74
+ beforeMessageSend: null,
75
+ afterMessageReceive: ' '
76
+ }));
77
+
78
+ expect(yml).not.toContain('scripts:');
79
+
80
+ const { script } = parseGrpcItem(yml);
81
+ expect(script!.beforeCallStart).toBeNull();
82
+ expect(script!.afterCallEnd).toBeNull();
83
+ expect(script!.beforeMessageSend).toBeNull();
84
+ expect(script!.afterMessageReceive).toBeNull();
85
+ });
86
+
87
+ it('keeps tests alongside the hooks', () => {
88
+ const yml = stringifyItem(
89
+ grpcItem({ beforeCallStart: 'bru.setVar("a", 1);', afterCallEnd: null }, 'test("ok", () => {});')
90
+ );
91
+
92
+ const request = parseGrpcItem(yml);
93
+ expect(request.script!.beforeCallStart).toBe('bru.setVar("a", 1);');
94
+ expect(request.tests).toBe('test("ok", () => {});');
95
+ });
96
+
97
+ it('survives a second round trip unchanged', () => {
98
+ const item = grpcItem({
99
+ beforeCallStart: 'bru.setVar("a", 1);',
100
+ afterCallEnd: 'bru.setVar("b", 2);',
101
+ beforeMessageSend: 'bru.setVar("c", 3);',
102
+ afterMessageReceive: 'bru.setVar("d", 4);'
103
+ });
104
+
105
+ const firstPass = stringifyItem(item);
106
+ const secondPass = stringifyItem(parseItem(firstPass));
107
+
108
+ expect(secondPass).toBe(firstPass);
109
+ });
110
+ });
@@ -2,19 +2,23 @@ import parseYmlItem from './parseItem';
2
2
  import parseYmlFolder from './parseFolder';
3
3
  import parseYmlCollection from './parseCollection';
4
4
  import parseYmlEnvironment from './parseEnvironment';
5
+ import parseYmlMockServer from './parseMockServer';
5
6
 
6
7
  import stringifyYmlItem from './stringifyItem';
7
8
  import stringifyYmlFolder from './stringifyFolder';
8
9
  import stringifyYmlCollection from './stringifyCollection';
9
10
  import stringifyYmlEnvironment from './stringifyEnvironment';
11
+ import stringifyYmlMockServer from './stringifyMockServer';
10
12
 
11
13
  export {
12
14
  parseYmlItem,
13
15
  parseYmlFolder,
14
16
  parseYmlCollection,
15
17
  parseYmlEnvironment,
18
+ parseYmlMockServer,
16
19
  stringifyYmlItem,
17
20
  stringifyYmlFolder,
18
21
  stringifyYmlCollection,
19
- stringifyYmlEnvironment
22
+ stringifyYmlEnvironment,
23
+ stringifyYmlMockServer
20
24
  };
@@ -9,6 +9,8 @@ import { toBrunoPostResponseVariables } from '../common/actions';
9
9
  import { toBrunoScripts } from '../common/scripts';
10
10
  import { toBrunoAssertions } from '../common/assertions';
11
11
  import { uuid, ensureString } from '../../../utils';
12
+ import { utils } from '@usebruno/common';
13
+ const { toBool, toMaxRedirects } = utils;
12
14
 
13
15
  const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
14
16
  const info = ocRequest.info;
@@ -110,11 +112,7 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
110
112
  if (ocRequest.settings) {
111
113
  const settings: BrunoHttpItemSettings = {};
112
114
 
113
- if (typeof ocRequest.settings.encodeUrl === 'boolean') {
114
- settings.encodeUrl = ocRequest.settings.encodeUrl;
115
- } else {
116
- settings.encodeUrl = true;
117
- }
115
+ settings.encodeUrl = toBool(ocRequest.settings.encodeUrl, true);
118
116
 
119
117
  if (typeof ocRequest.settings.timeout === 'number') {
120
118
  settings.timeout = ocRequest.settings.timeout;
@@ -124,17 +122,9 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
124
122
  settings.timeout = 0;
125
123
  }
126
124
 
127
- if (typeof ocRequest.settings.followRedirects === 'boolean') {
128
- settings.followRedirects = ocRequest.settings.followRedirects;
129
- } else {
130
- settings.followRedirects = true;
131
- }
132
-
133
- if (typeof ocRequest.settings.maxRedirects === 'number') {
134
- settings.maxRedirects = ocRequest.settings.maxRedirects;
135
- } else {
136
- settings.maxRedirects = 5;
137
- }
125
+ settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
126
+ settings.maxRedirects = toMaxRedirects(ocRequest.settings.maxRedirects);
127
+ settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
138
128
 
139
129
  brunoItem.settings = settings;
140
130
  }
@@ -51,8 +51,10 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
51
51
  grpc: []
52
52
  },
53
53
  script: {
54
- req: null,
55
- res: null
54
+ beforeCallStart: null,
55
+ beforeMessageSend: null,
56
+ afterMessageReceive: null,
57
+ afterCallEnd: null
56
58
  },
57
59
  vars: {
58
60
  req: [],
@@ -80,11 +82,17 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
80
82
  // scripts
81
83
  const scripts = toBrunoScripts(runtime?.scripts);
82
84
  if (scripts?.script && brunoRequest.script) {
83
- if (scripts.script.req) {
84
- brunoRequest.script.req = scripts.script.req;
85
+ if (scripts.script.beforeCallStart) {
86
+ brunoRequest.script.beforeCallStart = scripts.script.beforeCallStart;
85
87
  }
86
- if (scripts.script.res) {
87
- brunoRequest.script.res = scripts.script.res;
88
+ if (scripts.script.beforeMessageSend) {
89
+ brunoRequest.script.beforeMessageSend = scripts.script.beforeMessageSend;
90
+ }
91
+ if (scripts.script.afterMessageReceive) {
92
+ brunoRequest.script.afterMessageReceive = scripts.script.afterMessageReceive;
93
+ }
94
+ if (scripts.script.afterCallEnd) {
95
+ brunoRequest.script.afterCallEnd = scripts.script.afterCallEnd;
88
96
  }
89
97
  }
90
98
  if (scripts?.tests) {
@@ -10,7 +10,10 @@ import { toBrunoPostResponseVariables } from '../common/actions';
10
10
  import { toBrunoScripts } from '../common/scripts';
11
11
  import { toBrunoAssertions } from '../common/assertions';
12
12
  import { toBrunoApp } from '../common/app';
13
+ import { normalizeOmitHeaders } from '../common/omit-headers';
13
14
  import { uuid, ensureString } from '../../../utils';
15
+ import { utils } from '@usebruno/common';
16
+ const { toBool, toMaxRedirects } = utils;
14
17
 
15
18
  const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
16
19
  const info = ocRequest.info;
@@ -113,11 +116,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
113
116
  if (ocRequest.settings) {
114
117
  const settings: BrunoHttpItemSettings = {};
115
118
 
116
- if (typeof ocRequest.settings.encodeUrl === 'boolean') {
117
- settings.encodeUrl = ocRequest.settings.encodeUrl;
118
- } else {
119
- settings.encodeUrl = true;
120
- }
119
+ settings.encodeUrl = toBool(ocRequest.settings.encodeUrl, true);
121
120
 
122
121
  if (typeof ocRequest.settings.timeout === 'number') {
123
122
  settings.timeout = ocRequest.settings.timeout;
@@ -127,16 +126,13 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
127
126
  settings.timeout = 0;
128
127
  }
129
128
 
130
- if (typeof ocRequest.settings.followRedirects === 'boolean') {
131
- settings.followRedirects = ocRequest.settings.followRedirects;
132
- } else {
133
- settings.followRedirects = true;
134
- }
129
+ settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
130
+ settings.maxRedirects = toMaxRedirects(ocRequest.settings.maxRedirects);
131
+ settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
135
132
 
136
- if (typeof ocRequest.settings.maxRedirects === 'number') {
137
- settings.maxRedirects = ocRequest.settings.maxRedirects;
138
- } else {
139
- settings.maxRedirects = 5;
133
+ const omitHeaders = normalizeOmitHeaders(ocRequest.settings.omitHeaders);
134
+ if (omitHeaders) {
135
+ settings.omitHeaders = omitHeaders;
140
136
  }
141
137
 
142
138
  brunoItem.settings = settings;
@@ -8,7 +8,7 @@ import type { Assertion } from '@opencollection/types/common/assertions';
8
8
  import type { Action } from '@opencollection/types/common/actions';
9
9
  import type { HttpRequestParam, HttpRequestHeader } from '@opencollection/types/requests/http';
10
10
  import { stringifyYml } from '../utils';
11
- import { isNonEmptyString, isNumber } from '../../../utils';
11
+ import { isNonEmptyString } from '../../../utils';
12
12
  import { toOpenCollectionAuth } from '../common/auth';
13
13
  import { toOpenCollectionHttpHeaders } from '../common/headers';
14
14
  import { toOpenCollectionParams } from '../common/params';
@@ -16,6 +16,8 @@ import { toOpenCollectionVariables } from '../common/variables';
16
16
  import { toOpenCollectionActions } from '../common/actions';
17
17
  import { toOpenCollectionScripts } from '../common/scripts';
18
18
  import { toOpenCollectionAssertions } from '../common/assertions';
19
+ import { resolveTimeoutSetting, toMaxRedirects } from '@usebruno/common/utils';
20
+ import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
19
21
 
20
22
  const stringifyGraphQLRequest = (item: BrunoItem): string => {
21
23
  try {
@@ -96,7 +98,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
96
98
  }
97
99
 
98
100
  // scripts
99
- const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
101
+ const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
100
102
  if (scripts) {
101
103
  runtime.scripts = scripts;
102
104
  hasRuntime = true;
@@ -132,12 +134,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
132
134
  settings.encodeUrl = true;
133
135
  }
134
136
 
135
- const timeout = httpSettings?.timeout;
136
- if (isNumber(timeout)) {
137
- settings.timeout = timeout;
138
- } else {
139
- settings.timeout = 0;
140
- }
137
+ settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
141
138
 
142
139
  if (httpSettings?.followRedirects === true) {
143
140
  settings.followRedirects = true;
@@ -147,12 +144,9 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
147
144
  settings.followRedirects = true;
148
145
  }
149
146
 
150
- const maxRedirects = httpSettings?.maxRedirects;
151
- if (isNumber(maxRedirects)) {
152
- settings.maxRedirects = maxRedirects;
153
- } else {
154
- settings.maxRedirects = 5;
155
- }
147
+ settings.maxRedirects = toMaxRedirects(httpSettings?.maxRedirects);
148
+
149
+ settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
156
150
 
157
151
  ocRequest.settings = settings;
158
152
 
@@ -12,6 +12,7 @@ import { toOpenCollectionAuth } from '../common/auth';
12
12
  import { toOpenCollectionVariables } from '../common/variables';
13
13
  import { toOpenCollectionScripts } from '../common/scripts';
14
14
  import { toOpenCollectionAssertions } from '../common/assertions';
15
+ import { GRPC_SCRIPT_KEYS } from '@usebruno/common';
15
16
 
16
17
  const stringifyGrpcRequest = (item: BrunoItem): string => {
17
18
  try {
@@ -103,7 +104,7 @@ const stringifyGrpcRequest = (item: BrunoItem): string => {
103
104
  }
104
105
 
105
106
  // scripts
106
- const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
107
+ const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, GRPC_SCRIPT_KEYS);
107
108
  if (scripts) {
108
109
  runtime.scripts = scripts;
109
110
  hasRuntime = true;
@@ -17,7 +17,10 @@ import { toOpenCollectionVariables } from '../common/variables';
17
17
  import { toOpenCollectionActions } from '../common/actions';
18
18
  import { toOpenCollectionScripts } from '../common/scripts';
19
19
  import { toOpenCollectionAssertions } from '../common/assertions';
20
- import { isNumber, isNonEmptyString } from '../../../utils';
20
+ import { normalizeOmitHeaders } from '../common/omit-headers';
21
+ import { isNonEmptyString } from '../../../utils';
22
+ import { resolveTimeoutSetting, toMaxRedirects } from '@usebruno/common/utils';
23
+ import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
21
24
 
22
25
  const stringifyHttpRequest = (item: BrunoItem): string => {
23
26
  try {
@@ -84,7 +87,7 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
84
87
  }
85
88
 
86
89
  // scripts
87
- const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
90
+ const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
88
91
  if (scripts) {
89
92
  runtime.scripts = scripts;
90
93
  hasRuntime = true;
@@ -120,12 +123,7 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
120
123
  settings.encodeUrl = true;
121
124
  }
122
125
 
123
- const timeout = httpSettings?.timeout;
124
- if (isNumber(timeout)) {
125
- settings.timeout = timeout;
126
- } else {
127
- settings.timeout = 0;
128
- }
126
+ settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
129
127
 
130
128
  if (httpSettings?.followRedirects === true) {
131
129
  settings.followRedirects = true;
@@ -135,11 +133,13 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
135
133
  settings.followRedirects = true;
136
134
  }
137
135
 
138
- const maxRedirects = httpSettings?.maxRedirects;
139
- if (isNumber(maxRedirects)) {
140
- settings.maxRedirects = maxRedirects;
141
- } else {
142
- settings.maxRedirects = 5;
136
+ settings.maxRedirects = toMaxRedirects(httpSettings?.maxRedirects);
137
+
138
+ settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
139
+
140
+ const omitHeaders = normalizeOmitHeaders(httpSettings?.omitHeaders);
141
+ if (omitHeaders) {
142
+ settings.omitHeaders = omitHeaders;
143
143
  }
144
144
 
145
145
  ocRequest.settings = settings;
@@ -11,6 +11,7 @@ import { toOpenCollectionAuth } from '../common/auth';
11
11
  import { toOpenCollectionHttpHeaders } from '../common/headers';
12
12
  import { toOpenCollectionVariables } from '../common/variables';
13
13
  import { toOpenCollectionScripts } from '../common/scripts';
14
+ import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
14
15
 
15
16
  const stringifyWebsocketRequest = (item: BrunoItem): string => {
16
17
  try {
@@ -92,7 +93,8 @@ const stringifyWebsocketRequest = (item: BrunoItem): string => {
92
93
  }
93
94
 
94
95
  // scripts
95
- const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
96
+ // TODO: Modify to WS scripts once WS scripts are implemented.
97
+ const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
96
98
  if (scripts) {
97
99
  runtime.scripts = scripts;
98
100
  hasRuntime = true;
@@ -0,0 +1,95 @@
1
+ import type { HttpRequestHeader, HttpResponseHeader, HttpRequestParam, HttpRequestBody } from '@opencollection/types/requests/http';
2
+
3
+ export interface MockRuleCondition {
4
+ target?: string;
5
+ key?: string;
6
+ operator?: string;
7
+ value?: string;
8
+ }
9
+
10
+ export interface MockRouteRules {
11
+ operator?: 'AND' | 'OR';
12
+ conditions?: MockRuleCondition[];
13
+ }
14
+
15
+ export interface MockRouteEntry {
16
+ name?: string;
17
+ description?: string;
18
+ request?: {
19
+ method?: string;
20
+ url?: string;
21
+ headers?: HttpRequestHeader[];
22
+ params?: HttpRequestParam[];
23
+ body?: HttpRequestBody;
24
+ };
25
+ response?: {
26
+ status?: number;
27
+ statusText?: string;
28
+ headers?: HttpResponseHeader[];
29
+ body?: {
30
+ type?: string;
31
+ data?: string;
32
+ };
33
+ };
34
+ rules?: MockRouteRules;
35
+ copiedFrom?: {
36
+ example?: string | null;
37
+ requestPath?: string | null;
38
+ };
39
+ }
40
+
41
+ export interface MockServerFile {
42
+ info?: {
43
+ name?: string;
44
+ type?: 'mock';
45
+ };
46
+ mock?: {
47
+ port?: number;
48
+ delay?: number;
49
+ source?: {
50
+ type?: 'collection' | 'spec';
51
+ path?: string;
52
+ };
53
+ };
54
+ routes?: MockRouteEntry[];
55
+ }
56
+
57
+ export interface BrunoMockRoute {
58
+ name: string;
59
+ description: string;
60
+ request: {
61
+ url: string;
62
+ method: string;
63
+ headers: any[];
64
+ params: any[];
65
+ body: any;
66
+ };
67
+ response: {
68
+ status: number;
69
+ statusText: string;
70
+ headers: any[];
71
+ body: {
72
+ type: string;
73
+ content: string;
74
+ };
75
+ };
76
+ rules: {
77
+ operator: 'AND' | 'OR';
78
+ conditions: Array<{ target: string; key: string; operator: string; value: string }>;
79
+ };
80
+ copiedFrom?: {
81
+ exampleName: string | null;
82
+ requestPathname: string | null;
83
+ };
84
+ }
85
+
86
+ export interface BrunoMockServer {
87
+ name: string;
88
+ port: number | null;
89
+ delay: number;
90
+ source: {
91
+ type: 'collection' | 'spec';
92
+ path: string;
93
+ } | null;
94
+ routes: BrunoMockRoute[];
95
+ }
@@ -52,6 +52,129 @@ request:
52
52
  });
53
53
  });
54
54
 
55
+ describe('parseCollection — script execution flow', () => {
56
+ it('reads flow: sequential from the bruno extension', () => {
57
+ const yml = `opencollection: "1.0.0"
58
+ info:
59
+ name: c
60
+ extensions:
61
+ bruno:
62
+ scripts:
63
+ flow: sequential
64
+ `;
65
+ const { brunoConfig } = parseCollection(yml);
66
+ expect(brunoConfig.scripts?.flow).toBe('sequential');
67
+ });
68
+
69
+ it('reads flow: sandwich from the bruno extension', () => {
70
+ const yml = `opencollection: "1.0.0"
71
+ info:
72
+ name: c
73
+ extensions:
74
+ bruno:
75
+ scripts:
76
+ flow: sandwich
77
+ `;
78
+ const { brunoConfig } = parseCollection(yml);
79
+ expect(brunoConfig.scripts?.flow).toBe('sandwich');
80
+ });
81
+
82
+ it('ignores an unrecognized flow value', () => {
83
+ const yml = `opencollection: "1.0.0"
84
+ info:
85
+ name: c
86
+ extensions:
87
+ bruno:
88
+ scripts:
89
+ flow: parallel
90
+ `;
91
+ const { brunoConfig } = parseCollection(yml);
92
+ expect(brunoConfig.scripts?.flow).toBeUndefined();
93
+ });
94
+
95
+ it('has no scripts.flow when the file does not set one', () => {
96
+ const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n');
97
+ expect(brunoConfig.scripts?.flow).toBeUndefined();
98
+ });
99
+ });
100
+
101
+ describe('parseCollection — client certificates', () => {
102
+ it('maps pem and pkcs12 certs to Bruno cert/pfx shapes', () => {
103
+ const yml = `opencollection: "1.0.0"
104
+ info:
105
+ name: c
106
+ config:
107
+ clientCertificates:
108
+ - domain: localhost
109
+ type: pem
110
+ certificateFilePath: ./certs/client-cert.pem
111
+ privateKeyFilePath: ./certs/client-key.pem
112
+ passphrase: secret
113
+ - domain: example.com
114
+ type: pkcs12
115
+ pkcs12FilePath: ./certs/client.pfx
116
+ `;
117
+ const { brunoConfig } = parseCollection(yml);
118
+
119
+ expect(brunoConfig.clientCertificates.certs).toEqual([
120
+ {
121
+ domain: 'localhost',
122
+ type: 'cert',
123
+ certFilePath: './certs/client-cert.pem',
124
+ keyFilePath: './certs/client-key.pem',
125
+ passphrase: 'secret'
126
+ },
127
+ {
128
+ domain: 'example.com',
129
+ type: 'pfx',
130
+ pfxFilePath: './certs/client.pfx',
131
+ passphrase: ''
132
+ }
133
+ ]);
134
+ });
135
+
136
+ it('drops certs with an unrecognized type', () => {
137
+ const yml = `opencollection: "1.0.0"
138
+ info:
139
+ name: c
140
+ config:
141
+ clientCertificates:
142
+ - domain: localhost
143
+ type: bogus
144
+ certificateFilePath: ./certs/client-cert.pem
145
+ `;
146
+ const { brunoConfig } = parseCollection(yml);
147
+
148
+ expect(brunoConfig.clientCertificates.certs).toEqual([]);
149
+ });
150
+
151
+ it('reads a per-cert disabled flag when set to true, otherwise leaves it off', () => {
152
+ const yml = `opencollection: "1.0.0"
153
+ info:
154
+ name: c
155
+ config:
156
+ clientCertificates:
157
+ - domain: localhost
158
+ type: pem
159
+ certificateFilePath: ./certs/client-cert.pem
160
+ privateKeyFilePath: ./certs/client-key.pem
161
+ disabled: true
162
+ - domain: example.com
163
+ type: pkcs12
164
+ pkcs12FilePath: ./certs/client.pfx
165
+ disabled: true
166
+ - domain: other.com
167
+ type: pkcs12
168
+ pkcs12FilePath: ./certs/other.pfx
169
+ `;
170
+ const { brunoConfig } = parseCollection(yml);
171
+
172
+ expect(brunoConfig.clientCertificates.certs[0].disabled).toBe(true);
173
+ expect(brunoConfig.clientCertificates.certs[1].disabled).toBe(true);
174
+ expect(brunoConfig.clientCertificates.certs[2]).not.toHaveProperty('disabled');
175
+ });
176
+ });
177
+
55
178
  describe('parseCollection — reading the collection version', () => {
56
179
  it('reads the version from the file as text', () => {
57
180
  const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n version: v1.0.0\n');