@usebruno/filestore 0.10.0 → 0.12.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 (143) 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 +2 -0
  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/index.d.ts.map +1 -1
  12. package/dist/formats/bru/utils/redact-large-text-blocks.d.ts +11 -0
  13. package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -0
  14. package/dist/formats/yml/common/app.d.ts +8 -0
  15. package/dist/formats/yml/common/app.d.ts.map +1 -0
  16. package/dist/formats/yml/common/auth.d.ts.map +1 -1
  17. package/dist/formats/yml/common/body.d.ts.map +1 -1
  18. package/dist/formats/yml/common/datatype.d.ts +14 -0
  19. package/dist/formats/yml/common/datatype.d.ts.map +1 -0
  20. package/dist/formats/yml/common/headers.d.ts.map +1 -1
  21. package/dist/formats/yml/common/variables.d.ts +1 -2
  22. package/dist/formats/yml/common/variables.d.ts.map +1 -1
  23. package/dist/formats/yml/index.d.ts +3 -1
  24. package/dist/formats/yml/index.d.ts.map +1 -1
  25. package/dist/formats/yml/items/parseApp.d.ts +13 -0
  26. package/dist/formats/yml/items/parseApp.d.ts.map +1 -0
  27. package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
  28. package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
  29. package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
  30. package/dist/formats/yml/items/parseWebsocketRequest.d.ts.map +1 -1
  31. package/dist/formats/yml/items/stringifyApp.d.ts +4 -0
  32. package/dist/formats/yml/items/stringifyApp.d.ts.map +1 -0
  33. package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
  34. package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
  35. package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
  36. package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
  37. package/dist/formats/yml/mockServerTypes.d.ts +95 -0
  38. package/dist/formats/yml/mockServerTypes.d.ts.map +1 -0
  39. package/dist/formats/yml/parseCollection.d.ts.map +1 -1
  40. package/dist/formats/yml/parseEnvironment.d.ts +3 -1
  41. package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
  42. package/dist/formats/yml/parseFolder.d.ts.map +1 -1
  43. package/dist/formats/yml/parseItem.d.ts.map +1 -1
  44. package/dist/formats/yml/parseMockServer.d.ts +4 -0
  45. package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
  46. package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
  47. package/dist/formats/yml/stringifyEnvironment.d.ts +3 -1
  48. package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
  49. package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
  50. package/dist/formats/yml/stringifyItem.d.ts.map +1 -1
  51. package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
  52. package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
  53. package/dist/index.d.ts +18 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/types.d.ts +7 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/utils/secretsJsonToEnvironments.d.ts +38 -0
  58. package/dist/utils/secretsJsonToEnvironments.d.ts.map +1 -0
  59. package/dist/workers/WorkerQueue/index.d.ts +3 -2
  60. package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
  61. package/dist/workers/index.d.ts +4 -0
  62. package/dist/workers/index.d.ts.map +1 -1
  63. package/package.json +7 -5
  64. package/src/constants.ts +13 -0
  65. package/src/formats/bru/index.ts +52 -0
  66. package/src/formats/bru/tests/parseBruEnvironment.spec.js +63 -0
  67. package/src/formats/bru/tests/redact-large-text-blocks.spec.js +472 -0
  68. package/src/formats/bru/utils/redact-large-text-blocks.ts +143 -0
  69. package/src/formats/yml/common/actions.ts +1 -1
  70. package/src/formats/yml/common/app.ts +27 -0
  71. package/src/formats/yml/common/auth.ts +53 -1
  72. package/src/formats/yml/common/body.spec.ts +62 -0
  73. package/src/formats/yml/common/body.ts +27 -9
  74. package/src/formats/yml/common/datatype.spec.ts +110 -0
  75. package/src/formats/yml/common/datatype.ts +57 -0
  76. package/src/formats/yml/common/headers.ts +4 -0
  77. package/src/formats/yml/common/variables.spec.ts +127 -0
  78. package/src/formats/yml/common/variables.ts +14 -22
  79. package/src/formats/yml/index.ts +5 -1
  80. package/src/formats/yml/items/parseApp.ts +37 -0
  81. package/src/formats/yml/items/parseGraphQLRequest.ts +14 -16
  82. package/src/formats/yml/items/parseGrpcRequest.ts +24 -3
  83. package/src/formats/yml/items/parseHttpRequest.ts +19 -16
  84. package/src/formats/yml/items/parseWebsocketRequest.ts +29 -9
  85. package/src/formats/yml/items/stringifyApp.ts +45 -0
  86. package/src/formats/yml/items/stringifyGraphQLRequest.ts +7 -6
  87. package/src/formats/yml/items/stringifyGrpcRequest.ts +9 -11
  88. package/src/formats/yml/items/stringifyHttpRequest.ts +14 -6
  89. package/src/formats/yml/items/stringifyWebsocketRequest.ts +22 -9
  90. package/src/formats/yml/mockServerTypes.ts +95 -0
  91. package/src/formats/yml/parseCollection.spec.ts +193 -0
  92. package/src/formats/yml/parseCollection.ts +24 -6
  93. package/src/formats/yml/parseEnvironment.spec.ts +99 -0
  94. package/src/formats/yml/parseEnvironment.ts +41 -19
  95. package/src/formats/yml/parseFolder.spec.ts +70 -0
  96. package/src/formats/yml/parseFolder.ts +19 -22
  97. package/src/formats/yml/parseItem.spec.ts +147 -0
  98. package/src/formats/yml/parseItem.ts +4 -0
  99. package/src/formats/yml/parseMockServer.spec.ts +180 -0
  100. package/src/formats/yml/parseMockServer.ts +113 -0
  101. package/src/formats/yml/stringifyCollection.spec.ts +223 -0
  102. package/src/formats/yml/stringifyCollection.ts +26 -9
  103. package/src/formats/yml/stringifyEnvironment.spec.ts +91 -0
  104. package/src/formats/yml/stringifyEnvironment.ts +55 -20
  105. package/src/formats/yml/stringifyFolder.spec.ts +70 -0
  106. package/src/formats/yml/stringifyFolder.ts +7 -2
  107. package/src/formats/yml/stringifyItem.spec.ts +53 -0
  108. package/src/formats/yml/stringifyItem.ts +4 -0
  109. package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
  110. package/src/formats/yml/stringifyMockServer.ts +129 -0
  111. package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
  112. package/src/formats/yml/tests/fixtures/presets/empty-request.yml +7 -0
  113. package/src/formats/yml/tests/fixtures/presets/no-presets.yml +3 -0
  114. package/src/formats/yml/tests/fixtures/presets/no-request-key.yml +6 -0
  115. package/src/formats/yml/tests/fixtures/presets/type-only-realistic.yml +24 -0
  116. package/src/formats/yml/tests/fixtures/presets/url-only-realistic.yml +25 -0
  117. package/src/formats/yml/tests/fixtures/presets/with-type-and-url.yml +9 -0
  118. package/src/formats/yml/tests/parseCollection.spec.js +63 -0
  119. package/src/formats/yml/tests/parseEnvironment.spec.js +24 -63
  120. package/src/formats/yml/tests/parseEnvironment.spec.ts +160 -0
  121. package/src/formats/yml/tests/stringifyEnvironment.spec.ts +230 -0
  122. package/src/formats/yml/tests/variables.spec.js +7 -7
  123. package/src/formats/yml/utils.ts +1 -1
  124. package/src/index.ts +39 -1
  125. package/src/types/bruno-lang.d.ts +1 -0
  126. package/src/types.ts +8 -0
  127. package/src/utils/secretsJsonToEnvironments.spec.ts +211 -0
  128. package/src/utils/secretsJsonToEnvironments.ts +129 -0
  129. package/src/workers/WorkerQueue/index.ts +10 -2
  130. package/src/workers/index.ts +25 -1
  131. package/src/workers/worker-script.ts +45 -21
  132. package/dist/formats/bru/tests/example-status-swap.spec.d.ts +0 -2
  133. package/dist/formats/bru/tests/example-status-swap.spec.d.ts.map +0 -1
  134. package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts +0 -57
  135. package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts.map +0 -1
  136. package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts +0 -2
  137. package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts.map +0 -1
  138. package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts +0 -2
  139. package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts.map +0 -1
  140. package/dist/formats/yml/tests/parseEnvironment.spec.d.ts +0 -2
  141. package/dist/formats/yml/tests/parseEnvironment.spec.d.ts.map +0 -1
  142. package/dist/formats/yml/tests/variables.spec.d.ts +0 -2
  143. package/dist/formats/yml/tests/variables.spec.d.ts.map +0 -1
@@ -4,6 +4,7 @@ import stringifyGraphqlRequest from './items/stringifyGraphQLRequest';
4
4
  import stringifyGrpcRequest from './items/stringifyGrpcRequest';
5
5
  import stringifyWebsocketRequest from './items/stringifyWebsocketRequest';
6
6
  import stringifyScript from './items/stringifyScript';
7
+ import stringifyApp from './items/stringifyApp';
7
8
 
8
9
  const stringifyItem = (item: BrunoItem): string => {
9
10
  try {
@@ -23,6 +24,9 @@ const stringifyItem = (item: BrunoItem): string => {
23
24
  case 'js':
24
25
  return stringifyScript(item);
25
26
 
27
+ case 'app':
28
+ return stringifyApp(item);
29
+
26
30
  case 'folder':
27
31
  throw new Error('Folder items should be handled separately using stringifyFolder');
28
32
 
@@ -0,0 +1,177 @@
1
+ import stringifyMockServer from './stringifyMockServer';
2
+ import parseMockServer from './parseMockServer';
3
+ import type { BrunoMockServer } from './mockServerTypes';
4
+
5
+ const fullMockServer: BrunoMockServer = {
6
+ name: 'Dog API Mock',
7
+ port: 4001,
8
+ delay: 500,
9
+ source: {
10
+ type: 'collection',
11
+ path: '/Users/x/collections/dog-api'
12
+ },
13
+ routes: [
14
+ {
15
+ name: 'Get user 200',
16
+ description: 'Returns a user',
17
+ request: {
18
+ url: '/users/:id',
19
+ method: 'GET',
20
+ headers: [{ uid: 'h1', name: 'X-Client', value: 'bruno', enabled: true }],
21
+ params: [{ uid: 'p1', name: 'id', value: '1', type: 'path', enabled: true }],
22
+ body: { mode: 'json', json: '{"filter": true}' }
23
+ },
24
+ response: {
25
+ status: 200,
26
+ statusText: 'OK',
27
+ headers: [{ uid: 'rh1', name: 'Content-Type', value: 'application/json', enabled: true }],
28
+ body: {
29
+ type: 'json',
30
+ content: '{"id": 1}'
31
+ }
32
+ },
33
+ rules: {
34
+ operator: 'AND',
35
+ conditions: [
36
+ { target: 'header', key: 'Authorization', operator: 'equals', value: 'token' }
37
+ ]
38
+ },
39
+ copiedFrom: {
40
+ exampleName: 'Success',
41
+ requestPathname: '/Users/x/collections/dog-api/get-user.yml'
42
+ }
43
+ }
44
+ ]
45
+ };
46
+
47
+ describe('stringifyMockServer', () => {
48
+ it('writes the info block with type mock', () => {
49
+ const content = stringifyMockServer(fullMockServer);
50
+
51
+ expect(content).toContain('info:');
52
+ expect(content).toContain('name: Dog API Mock');
53
+ expect(content).toContain('type: mock');
54
+ });
55
+
56
+ it('writes port, delay and source under the mock block', () => {
57
+ const content = stringifyMockServer(fullMockServer);
58
+
59
+ expect(content).toContain('mock:');
60
+ expect(content).toContain('port: 4001');
61
+ expect(content).toContain('delay: 500');
62
+ expect(content).toContain('type: collection');
63
+ expect(content).toContain('path: /Users/x/collections/dog-api');
64
+ });
65
+
66
+ it('omits delay when zero and source when manual', () => {
67
+ const content = stringifyMockServer({
68
+ name: 'Manual Mock',
69
+ port: 4000,
70
+ delay: 0,
71
+ source: null,
72
+ routes: []
73
+ });
74
+
75
+ expect(content).not.toContain('delay:');
76
+ expect(content).not.toContain('source:');
77
+ expect(content).not.toContain('routes:');
78
+ });
79
+
80
+ it('omits rules when there are no conditions', () => {
81
+ const content = stringifyMockServer({
82
+ name: 'Mock',
83
+ port: 4000,
84
+ delay: 0,
85
+ source: null,
86
+ routes: [
87
+ {
88
+ name: 'Ping',
89
+ description: '',
90
+ request: { url: '/ping', method: 'GET', headers: [], params: [], body: { mode: 'none' } },
91
+ response: { status: 204, statusText: 'No Content', headers: [], body: { type: 'text', content: '' } },
92
+ rules: { operator: 'AND', conditions: [] }
93
+ }
94
+ ]
95
+ });
96
+
97
+ expect(content).not.toContain('rules:');
98
+ });
99
+
100
+ it('does not write uids anywhere', () => {
101
+ const content = stringifyMockServer(fullMockServer);
102
+ expect(content).not.toContain('uid');
103
+ });
104
+ });
105
+
106
+ describe('mock server round-trip', () => {
107
+ it('parse(stringify(x)) preserves all fields', () => {
108
+ const parsed = parseMockServer(stringifyMockServer(fullMockServer));
109
+
110
+ expect(parsed.name).toBe('Dog API Mock');
111
+ expect(parsed.port).toBe(4001);
112
+ expect(parsed.delay).toBe(500);
113
+ expect(parsed.source).toEqual({ type: 'collection', path: '/Users/x/collections/dog-api' });
114
+
115
+ expect(parsed.routes).toHaveLength(1);
116
+ const route = parsed.routes[0];
117
+ expect(route.name).toBe('Get user 200');
118
+ expect(route.description).toBe('Returns a user');
119
+ expect(route.request.url).toBe('/users/:id');
120
+ expect(route.request.method).toBe('GET');
121
+ expect(route.request.headers).toEqual([
122
+ expect.objectContaining({ name: 'X-Client', value: 'bruno', enabled: true })
123
+ ]);
124
+ expect(route.request.params).toEqual([
125
+ expect.objectContaining({ name: 'id', value: '1', type: 'path', enabled: true })
126
+ ]);
127
+ expect(route.request.body).toEqual(expect.objectContaining({ mode: 'json', json: '{"filter": true}' }));
128
+ expect(route.response.status).toBe(200);
129
+ expect(route.response.statusText).toBe('OK');
130
+ expect(route.response.headers).toEqual([
131
+ expect.objectContaining({ name: 'Content-Type', value: 'application/json' })
132
+ ]);
133
+ expect(route.response.body).toEqual({ type: 'json', content: '{"id": 1}' });
134
+ expect(route.rules).toEqual({
135
+ operator: 'AND',
136
+ conditions: [{ target: 'header', key: 'Authorization', operator: 'equals', value: 'token' }]
137
+ });
138
+ expect(route.copiedFrom).toEqual({
139
+ exampleName: 'Success',
140
+ requestPathname: '/Users/x/collections/dog-api/get-user.yml'
141
+ });
142
+ });
143
+
144
+ it('round-trips a spec-sourced server', () => {
145
+ const parsed = parseMockServer(stringifyMockServer({
146
+ name: 'Spec Mock',
147
+ port: 4002,
148
+ delay: 0,
149
+ source: { type: 'spec', path: '/Users/x/specs/petstore.yml' },
150
+ routes: []
151
+ }));
152
+
153
+ expect(parsed.source).toEqual({ type: 'spec', path: '/Users/x/specs/petstore.yml' });
154
+ expect(parsed.routes).toEqual([]);
155
+ });
156
+
157
+ it('round-trips multiline json response bodies byte-safely', () => {
158
+ const content = '{\n "id": 1,\n "name": "rex: \'good\' dog"\n}';
159
+ const parsed = parseMockServer(stringifyMockServer({
160
+ name: 'Mock',
161
+ port: 4000,
162
+ delay: 0,
163
+ source: null,
164
+ routes: [
165
+ {
166
+ name: 'Get dog',
167
+ description: '',
168
+ request: { url: '/dogs/1', method: 'GET', headers: [], params: [], body: { mode: 'none' } },
169
+ response: { status: 200, statusText: 'OK', headers: [], body: { type: 'json', content } },
170
+ rules: { operator: 'AND', conditions: [] }
171
+ }
172
+ ]
173
+ }));
174
+
175
+ expect(parsed.routes[0].response.body.content).toBe(content);
176
+ });
177
+ });
@@ -0,0 +1,129 @@
1
+ import { stringifyYml } from './utils';
2
+ import { isNonEmptyString } from '../../utils';
3
+ import { toOpenCollectionHttpHeaders, toOpenCollectionResponseHeaders } from './common/headers';
4
+ import { toOpenCollectionParams } from './common/params';
5
+ import { toOpenCollectionBody } from './common/body';
6
+ import type {
7
+ MockServerFile,
8
+ MockRouteEntry,
9
+ BrunoMockServer,
10
+ BrunoMockRoute
11
+ } from './mockServerTypes';
12
+
13
+ const toOcRuleConditions = (conditions: BrunoMockRoute['rules']['conditions']) => (
14
+ conditions.map((condition) => ({
15
+ target: condition.target || '',
16
+ key: condition.key || '',
17
+ operator: condition.operator || 'equals',
18
+ value: condition.value || ''
19
+ }))
20
+ );
21
+
22
+ const toOcMockRoute = (route: BrunoMockRoute): MockRouteEntry => {
23
+ const ocRoute: MockRouteEntry = {
24
+ name: route.name || 'Untitled Mock Response'
25
+ };
26
+
27
+ if (isNonEmptyString(route.description)) {
28
+ ocRoute.description = route.description;
29
+ }
30
+
31
+ ocRoute.request = {
32
+ method: route.request?.method || 'GET',
33
+ url: route.request?.url || '/'
34
+ };
35
+
36
+ const requestHeaders = toOpenCollectionHttpHeaders(route.request?.headers);
37
+ if (requestHeaders) {
38
+ ocRoute.request.headers = requestHeaders;
39
+ }
40
+
41
+ const requestParams = toOpenCollectionParams(route.request?.params);
42
+ if (requestParams) {
43
+ ocRoute.request.params = requestParams;
44
+ }
45
+
46
+ const requestBody = toOpenCollectionBody(route.request?.body);
47
+ if (requestBody !== undefined && !Array.isArray(requestBody)) {
48
+ ocRoute.request.body = requestBody;
49
+ }
50
+
51
+ ocRoute.response = {
52
+ status: Number(route.response?.status) || 200
53
+ };
54
+
55
+ if (isNonEmptyString(route.response?.statusText)) {
56
+ ocRoute.response.statusText = route.response.statusText;
57
+ }
58
+
59
+ const responseHeaders = toOpenCollectionResponseHeaders(route.response?.headers);
60
+ if (responseHeaders) {
61
+ ocRoute.response.headers = responseHeaders;
62
+ }
63
+
64
+ const responseBody = route.response?.body;
65
+ if (responseBody && responseBody.type) {
66
+ const content = responseBody.content;
67
+ ocRoute.response.body = {
68
+ type: responseBody.type,
69
+ data: typeof content === 'string' ? content : JSON.stringify(content, null, 2)
70
+ };
71
+ }
72
+
73
+ if (route.rules?.conditions?.length) {
74
+ ocRoute.rules = {
75
+ operator: route.rules.operator === 'OR' ? 'OR' : 'AND',
76
+ conditions: toOcRuleConditions(route.rules.conditions)
77
+ };
78
+ }
79
+
80
+ if (route.copiedFrom && (route.copiedFrom.exampleName || route.copiedFrom.requestPathname)) {
81
+ ocRoute.copiedFrom = {};
82
+ if (route.copiedFrom.exampleName) {
83
+ ocRoute.copiedFrom.example = route.copiedFrom.exampleName;
84
+ }
85
+ if (route.copiedFrom.requestPathname) {
86
+ ocRoute.copiedFrom.requestPath = route.copiedFrom.requestPathname;
87
+ }
88
+ }
89
+
90
+ return ocRoute;
91
+ };
92
+
93
+ const stringifyMockServer = (mockServer: BrunoMockServer): string => {
94
+ try {
95
+ const ocMockServer: MockServerFile = {
96
+ info: {
97
+ name: mockServer.name && mockServer.name.trim().length ? mockServer.name : 'Mock Server',
98
+ type: 'mock'
99
+ },
100
+ mock: {
101
+ port: Number(mockServer.port) || 4000
102
+ }
103
+ };
104
+
105
+ const delay = Number(mockServer.delay);
106
+ if (delay > 0) {
107
+ ocMockServer.mock!.delay = delay;
108
+ }
109
+
110
+ const source = mockServer.source;
111
+ if (source && (source.type === 'collection' || source.type === 'spec') && source.path) {
112
+ ocMockServer.mock!.source = {
113
+ type: source.type,
114
+ path: source.path
115
+ };
116
+ }
117
+
118
+ if (mockServer.routes?.length) {
119
+ ocMockServer.routes = mockServer.routes.map(toOcMockRoute);
120
+ }
121
+
122
+ return stringifyYml(ocMockServer);
123
+ } catch (error) {
124
+ console.error('Error stringifying mock server:', error);
125
+ throw error;
126
+ }
127
+ };
128
+
129
+ export default stringifyMockServer;
@@ -0,0 +1,50 @@
1
+ import parseCollection from '../parseCollection';
2
+ import stringifyCollection from '../stringifyCollection';
3
+
4
+ const collectionRoot = { meta: null, request: null, docs: null };
5
+
6
+ describe('yml collection - presets.defaultEnvironment round-trip', () => {
7
+ it('writes and reads back defaultEnvironment via extensions.bruno.presets', () => {
8
+ const brunoConfig = {
9
+ name: 'My API',
10
+ type: 'collection',
11
+ ignore: ['node_modules'],
12
+ presets: { requestType: 'http', requestUrl: 'https://example.com', defaultEnvironment: 'prod' }
13
+ };
14
+
15
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
16
+ expect(yml).toContain('defaultEnvironment: prod');
17
+
18
+ const { brunoConfig: parsed } = parseCollection(yml);
19
+ expect(parsed.presets).toEqual({
20
+ requestType: 'http',
21
+ requestUrl: 'https://example.com',
22
+ defaultEnvironment: 'prod'
23
+ });
24
+ });
25
+
26
+ it('persists defaultEnvironment even when no request presets are set', () => {
27
+ const brunoConfig = {
28
+ name: 'My API',
29
+ type: 'collection',
30
+ ignore: [],
31
+ presets: { defaultEnvironment: 'staging' }
32
+ };
33
+
34
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
35
+ expect(yml).toContain('defaultEnvironment: staging');
36
+
37
+ const { brunoConfig: parsed } = parseCollection(yml);
38
+ expect(parsed.presets.defaultEnvironment).toBe('staging');
39
+ });
40
+
41
+ it('does not emit defaultEnvironment when it is not configured', () => {
42
+ const brunoConfig = { name: 'My API', type: 'collection', ignore: [] };
43
+
44
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
45
+ expect(yml).not.toContain('defaultEnvironment');
46
+
47
+ const { brunoConfig: parsed } = parseCollection(yml);
48
+ expect(parsed.presets).toBeUndefined();
49
+ });
50
+ });
@@ -0,0 +1,7 @@
1
+ opencollection: 1.0.0
2
+ info:
3
+ name: My Collection
4
+ extensions:
5
+ bruno:
6
+ presets:
7
+ request: {}
@@ -0,0 +1,3 @@
1
+ opencollection: 1.0.0
2
+ info:
3
+ name: My Collection
@@ -0,0 +1,6 @@
1
+ opencollection: 1.0.0
2
+ info:
3
+ name: My Collection
4
+ extensions:
5
+ bruno:
6
+ presets: {}
@@ -0,0 +1,24 @@
1
+ opencollection: 1.0.0
2
+
3
+ info:
4
+ name: md8
5
+ config:
6
+ proxy:
7
+ inherit: true
8
+ config:
9
+ protocol: http
10
+ hostname: ""
11
+ port: ""
12
+ auth:
13
+ username: ""
14
+ password: ""
15
+ bypassProxy: ""
16
+ bundled: false
17
+ extensions:
18
+ bruno:
19
+ ignore:
20
+ - node_modules
21
+ - .git
22
+ presets:
23
+ request:
24
+ type: graphql
@@ -0,0 +1,25 @@
1
+ opencollection: 1.0.0
2
+
3
+ info:
4
+ name: md8
5
+ config:
6
+ proxy:
7
+ inherit: true
8
+ config:
9
+ protocol: http
10
+ hostname: ""
11
+ port: ""
12
+ auth:
13
+ username: ""
14
+ password: ""
15
+ bypassProxy: ""
16
+ bundled: false
17
+ extensions:
18
+ bruno:
19
+ ignore:
20
+ - node_modules
21
+ - .git
22
+ presets:
23
+ request:
24
+ type: http
25
+ url: https://example.com/graphql
@@ -0,0 +1,9 @@
1
+ opencollection: 1.0.0
2
+ info:
3
+ name: My Collection
4
+ extensions:
5
+ bruno:
6
+ presets:
7
+ request:
8
+ type: graphql
9
+ url: https://example.com/graphql
@@ -0,0 +1,63 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import parseCollection from '../parseCollection';
4
+
5
+ const loadFixture = (name) =>
6
+ fs.readFileSync(path.join(__dirname, 'fixtures', 'presets', `${name}.yml`), 'utf8');
7
+
8
+ describe('yml parseCollection - presets', () => {
9
+ it('parses presets when request.type and request.url are present', () => {
10
+ const { brunoConfig } = parseCollection(loadFixture('with-type-and-url'));
11
+
12
+ expect(brunoConfig.presets).toEqual({
13
+ requestType: 'graphql',
14
+ requestUrl: 'https://example.com/graphql'
15
+ });
16
+ });
17
+
18
+ it('defaults requestType and requestUrl to empty strings (not arrays) when request fields are missing', () => {
19
+ const { brunoConfig } = parseCollection(loadFixture('empty-request'));
20
+
21
+ expect(brunoConfig.presets).toEqual({
22
+ requestType: '',
23
+ requestUrl: ''
24
+ });
25
+ expect(Array.isArray(brunoConfig.presets.requestType)).toBe(false);
26
+ expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
27
+ });
28
+
29
+ it('does not set presets when the extension has no presets block', () => {
30
+ const { brunoConfig } = parseCollection(loadFixture('no-presets'));
31
+
32
+ expect(brunoConfig.presets).toBeUndefined();
33
+ });
34
+
35
+ it('does not set presets when presets exists but request key is absent', () => {
36
+ const { brunoConfig } = parseCollection(loadFixture('no-request-key'));
37
+
38
+ expect(brunoConfig.presets).toBeUndefined();
39
+ });
40
+
41
+ it('parses a realistic collection with only request.type set (no url) — defaults url to empty string', () => {
42
+ const { brunoConfig } = parseCollection(loadFixture('type-only-realistic'));
43
+
44
+ expect(brunoConfig.presets).toEqual({
45
+ requestType: 'graphql',
46
+ requestUrl: ''
47
+ });
48
+ expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
49
+ expect(brunoConfig.ignore).toEqual(['node_modules', '.git']);
50
+ });
51
+
52
+ it('parses a realistic collection with request.type http and request.url set', () => {
53
+ const { brunoConfig } = parseCollection(loadFixture('url-only-realistic'));
54
+
55
+ expect(brunoConfig.presets).toEqual({
56
+ requestType: 'http',
57
+ requestUrl: 'https://example.com/graphql'
58
+ });
59
+ expect(Array.isArray(brunoConfig.presets.requestType)).toBe(false);
60
+ expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
61
+ expect(brunoConfig.ignore).toEqual(['node_modules', '.git']);
62
+ });
63
+ });
@@ -48,46 +48,46 @@ variables:
48
48
  const byName = (env) => Object.fromEntries(env.variables.map((v) => [v.name, v]));
49
49
 
50
50
  describe('yml parseEnvironment - typed values', () => {
51
- it('keeps the value as a string and preserves the type via datatype', () => {
51
+ it('coerces the value to its native JS type and preserves the type via dataType', () => {
52
52
  const variables = byName(parseEnvironment(ENV_YML));
53
53
 
54
- expect(variables.env_num).toMatchObject({ value: '300', type: 'text', datatype: 'number' });
55
- expect(typeof variables.env_num.value).toBe('string');
54
+ expect(variables.env_num).toMatchObject({ value: 300, type: 'text', dataType: 'number' });
55
+ expect(typeof variables.env_num.value).toBe('number');
56
56
 
57
- expect(variables.env_bool).toMatchObject({ value: 'true', datatype: 'boolean' });
58
- expect(variables.falsy_num).toMatchObject({ value: '0', datatype: 'number' });
59
- expect(variables.falsy_bool).toMatchObject({ value: 'false', datatype: 'boolean' });
57
+ expect(variables.env_bool).toMatchObject({ value: true, dataType: 'boolean' });
58
+ expect(variables.falsy_num).toMatchObject({ value: 0, dataType: 'number' });
59
+ expect(variables.falsy_bool).toMatchObject({ value: false, dataType: 'boolean' });
60
60
 
61
- expect(variables.env_obj.datatype).toBe('object');
62
- expect(typeof variables.env_obj.value).toBe('string');
63
- expect(variables.env_obj.value).toContain('"scope": "env"');
61
+ expect(variables.env_obj.dataType).toBe('object');
62
+ expect(typeof variables.env_obj.value).toBe('object');
63
+ expect(variables.env_obj.value).toMatchObject({ scope: 'env' });
64
64
 
65
- expect(variables.env_array_obj).toMatchObject({ value: '[1,2,3,4]', datatype: 'object' });
65
+ expect(variables.env_array_obj).toMatchObject({ value: [1, 2, 3, 4], dataType: 'object' });
66
66
  });
67
67
 
68
- it('does not set datatype for plain string values', () => {
68
+ it('does not set dataType for plain string values', () => {
69
69
  const variables = byName(parseEnvironment(ENV_YML));
70
70
 
71
71
  expect(variables.env_str).toMatchObject({ value: 'env_string', type: 'text', secret: false });
72
- expect(variables.env_str).not.toHaveProperty('datatype');
72
+ expect(variables.env_str).not.toHaveProperty('dataType');
73
73
  });
74
74
 
75
- it('parses secret variables with no value or datatype', () => {
75
+ it('parses secret variables with no value or dataType', () => {
76
76
  const variables = byName(parseEnvironment(ENV_YML));
77
77
 
78
78
  expect(variables.env_secret_str).toMatchObject({ name: 'env_secret_str', value: '', secret: true });
79
- expect(variables.env_secret_str).not.toHaveProperty('datatype');
79
+ expect(variables.env_secret_str).not.toHaveProperty('dataType');
80
80
  });
81
81
 
82
- it('parses secret variables with a type, keeping the value empty and the type in datatype', () => {
82
+ it('parses secret variables with a type, keeping the value empty and the type in dataType', () => {
83
83
  const variables = byName(parseEnvironment(ENV_YML));
84
84
 
85
- expect(variables.env_secret_num).toMatchObject({ value: '', secret: true, datatype: 'number' });
86
- expect(variables.env_secret_bool).toMatchObject({ value: '', secret: true, datatype: 'boolean' });
87
- expect(variables.env_secret_obj).toMatchObject({ value: '', secret: true, datatype: 'object' });
85
+ expect(variables.env_secret_num).toMatchObject({ value: '', secret: true, dataType: 'number' });
86
+ expect(variables.env_secret_bool).toMatchObject({ value: '', secret: true, dataType: 'boolean' });
87
+ expect(variables.env_secret_obj).toMatchObject({ value: '', secret: true, dataType: 'object' });
88
88
  });
89
89
 
90
- it('serializes secret variable datatype back to a type field, omitting it for plain secrets', () => {
90
+ it('serializes secret variable dataType back to a type field, omitting it for plain secrets', () => {
91
91
  const yml = stringifyEnvironment(parseEnvironment(ENV_YML));
92
92
 
93
93
  expect(yml).toContain('- secret: true\n name: env_secret_num\n type: number');
@@ -97,19 +97,19 @@ describe('yml parseEnvironment - typed values', () => {
97
97
  expect(yml).not.toContain('name: env_secret_str\n type:');
98
98
  });
99
99
 
100
- it('serializes datatype back to an OpenCollection { type, data } value', () => {
100
+ it('serializes dataType back to an OpenCollection { type, data } value', () => {
101
101
  const yml = stringifyEnvironment(parseEnvironment(ENV_YML));
102
102
 
103
103
  expect(yml).toContain('type: number');
104
104
  expect(yml).toContain('data: "300"');
105
105
  expect(yml).toContain('type: boolean');
106
106
  expect(yml).toContain('type: object');
107
- // plain strings stay plain, never wrapped as a string datatype
107
+ // plain strings stay plain, never wrapped as a string dataType
108
108
  expect(yml).toContain('value: env_string');
109
109
  expect(yml).not.toContain('type: string');
110
110
  });
111
111
 
112
- it('round-trips value and datatype through parse -> stringify -> parse', () => {
112
+ it('round-trips value and dataType through parse -> stringify -> parse', () => {
113
113
  const env = parseEnvironment(ENV_YML);
114
114
  const reparsed = parseEnvironment(stringifyEnvironment(env));
115
115
 
@@ -118,44 +118,5 @@ describe('yml parseEnvironment - typed values', () => {
118
118
  });
119
119
  });
120
120
 
121
- const EXTERNAL_SECRETS_YML = `name: test_env
122
- variables:
123
- - name: env_str
124
- value: env_string
125
- externalSecrets:
126
- type: my-vault
127
- variables:
128
- - name: by_path
129
- path: secret/data/secret
130
- - name: by_secret_name
131
- secretName: secret
132
- - name: by_vault_name
133
- vaultName: secret
134
- `;
135
-
136
- describe('yml parseEnvironment - external secrets', () => {
137
- it('parses externalSecrets, preserving the type and arbitrary variable keys', () => {
138
- const env = parseEnvironment(EXTERNAL_SECRETS_YML);
139
-
140
- expect(env.externalSecrets).toEqual({
141
- type: 'my-vault',
142
- variables: [
143
- { name: 'by_path', path: 'secret/data/secret' },
144
- { name: 'by_secret_name', secretName: 'secret' },
145
- { name: 'by_vault_name', vaultName: 'secret' }
146
- ]
147
- });
148
- });
149
-
150
- it('does not set externalSecrets when the yml has none', () => {
151
- const env = parseEnvironment('name: test_env\nvariables: []\n');
152
- expect(env.externalSecrets).toBeUndefined();
153
- });
154
-
155
- it('round-trips externalSecrets through parse -> stringify -> parse', () => {
156
- const env = parseEnvironment(EXTERNAL_SECRETS_YML);
157
- const reparsed = parseEnvironment(stringifyEnvironment(env));
158
-
159
- expect(reparsed.externalSecrets).toEqual(env.externalSecrets);
160
- });
161
- });
121
+ // External-secrets parsing/serialization is covered in parseEnvironment.spec.ts and
122
+ // stringifyEnvironment.spec.ts against the strict { type, variables } model.