@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.
- 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 +2 -0
- 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/index.d.ts.map +1 -1
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts +11 -0
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -0
- package/dist/formats/yml/common/app.d.ts +8 -0
- package/dist/formats/yml/common/app.d.ts.map +1 -0
- package/dist/formats/yml/common/auth.d.ts.map +1 -1
- package/dist/formats/yml/common/body.d.ts.map +1 -1
- package/dist/formats/yml/common/datatype.d.ts +14 -0
- package/dist/formats/yml/common/datatype.d.ts.map +1 -0
- package/dist/formats/yml/common/headers.d.ts.map +1 -1
- package/dist/formats/yml/common/variables.d.ts +1 -2
- package/dist/formats/yml/common/variables.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/parseApp.d.ts +13 -0
- package/dist/formats/yml/items/parseApp.d.ts.map +1 -0
- 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/parseWebsocketRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyApp.d.ts +4 -0
- package/dist/formats/yml/items/stringifyApp.d.ts.map +1 -0
- 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 +3 -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/parseItem.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 +3 -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/stringifyItem.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 +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/secretsJsonToEnvironments.d.ts +38 -0
- package/dist/utils/secretsJsonToEnvironments.d.ts.map +1 -0
- 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 +7 -5
- package/src/constants.ts +13 -0
- package/src/formats/bru/index.ts +52 -0
- package/src/formats/bru/tests/parseBruEnvironment.spec.js +63 -0
- package/src/formats/bru/tests/redact-large-text-blocks.spec.js +472 -0
- package/src/formats/bru/utils/redact-large-text-blocks.ts +143 -0
- package/src/formats/yml/common/actions.ts +1 -1
- package/src/formats/yml/common/app.ts +27 -0
- package/src/formats/yml/common/auth.ts +53 -1
- package/src/formats/yml/common/body.spec.ts +62 -0
- package/src/formats/yml/common/body.ts +27 -9
- package/src/formats/yml/common/datatype.spec.ts +110 -0
- package/src/formats/yml/common/datatype.ts +57 -0
- package/src/formats/yml/common/headers.ts +4 -0
- package/src/formats/yml/common/variables.spec.ts +127 -0
- package/src/formats/yml/common/variables.ts +14 -22
- package/src/formats/yml/index.ts +5 -1
- package/src/formats/yml/items/parseApp.ts +37 -0
- package/src/formats/yml/items/parseGraphQLRequest.ts +14 -16
- package/src/formats/yml/items/parseGrpcRequest.ts +24 -3
- package/src/formats/yml/items/parseHttpRequest.ts +19 -16
- package/src/formats/yml/items/parseWebsocketRequest.ts +29 -9
- package/src/formats/yml/items/stringifyApp.ts +45 -0
- package/src/formats/yml/items/stringifyGraphQLRequest.ts +7 -6
- package/src/formats/yml/items/stringifyGrpcRequest.ts +9 -11
- package/src/formats/yml/items/stringifyHttpRequest.ts +14 -6
- package/src/formats/yml/items/stringifyWebsocketRequest.ts +22 -9
- package/src/formats/yml/mockServerTypes.ts +95 -0
- package/src/formats/yml/parseCollection.spec.ts +193 -0
- package/src/formats/yml/parseCollection.ts +24 -6
- package/src/formats/yml/parseEnvironment.spec.ts +99 -0
- package/src/formats/yml/parseEnvironment.ts +41 -19
- package/src/formats/yml/parseFolder.spec.ts +70 -0
- package/src/formats/yml/parseFolder.ts +19 -22
- package/src/formats/yml/parseItem.spec.ts +147 -0
- package/src/formats/yml/parseItem.ts +4 -0
- package/src/formats/yml/parseMockServer.spec.ts +180 -0
- package/src/formats/yml/parseMockServer.ts +113 -0
- package/src/formats/yml/stringifyCollection.spec.ts +223 -0
- package/src/formats/yml/stringifyCollection.ts +26 -9
- package/src/formats/yml/stringifyEnvironment.spec.ts +91 -0
- package/src/formats/yml/stringifyEnvironment.ts +55 -20
- package/src/formats/yml/stringifyFolder.spec.ts +70 -0
- package/src/formats/yml/stringifyFolder.ts +7 -2
- package/src/formats/yml/stringifyItem.spec.ts +53 -0
- package/src/formats/yml/stringifyItem.ts +4 -0
- 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/fixtures/presets/empty-request.yml +7 -0
- package/src/formats/yml/tests/fixtures/presets/no-presets.yml +3 -0
- package/src/formats/yml/tests/fixtures/presets/no-request-key.yml +6 -0
- package/src/formats/yml/tests/fixtures/presets/type-only-realistic.yml +24 -0
- package/src/formats/yml/tests/fixtures/presets/url-only-realistic.yml +25 -0
- package/src/formats/yml/tests/fixtures/presets/with-type-and-url.yml +9 -0
- package/src/formats/yml/tests/parseCollection.spec.js +63 -0
- package/src/formats/yml/tests/parseEnvironment.spec.js +24 -63
- package/src/formats/yml/tests/parseEnvironment.spec.ts +160 -0
- package/src/formats/yml/tests/stringifyEnvironment.spec.ts +230 -0
- package/src/formats/yml/tests/variables.spec.js +7 -7
- package/src/formats/yml/utils.ts +1 -1
- package/src/index.ts +39 -1
- package/src/types/bruno-lang.d.ts +1 -0
- package/src/types.ts +8 -0
- package/src/utils/secretsJsonToEnvironments.spec.ts +211 -0
- package/src/utils/secretsJsonToEnvironments.ts +129 -0
- package/src/workers/WorkerQueue/index.ts +10 -2
- package/src/workers/index.ts +25 -1
- package/src/workers/worker-script.ts +45 -21
- package/dist/formats/bru/tests/example-status-swap.spec.d.ts +0 -2
- package/dist/formats/bru/tests/example-status-swap.spec.d.ts.map +0 -1
- package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts +0 -57
- package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts.map +0 -1
- package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts +0 -2
- package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts.map +0 -1
- package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts +0 -2
- package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts.map +0 -1
- package/dist/formats/yml/tests/parseEnvironment.spec.d.ts +0 -2
- package/dist/formats/yml/tests/parseEnvironment.spec.d.ts.map +0 -1
- package/dist/formats/yml/tests/variables.spec.d.ts +0 -2
- package/dist/formats/yml/tests/variables.spec.d.ts.map +0 -1
|
@@ -8,6 +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, HttpRequestBody } from '@opencollection/types/requests/http';
|
|
10
10
|
import { stringifyYml } from '../utils';
|
|
11
|
+
import { toOpenCollectionApp, OpenCollectionApp } from '../common/app';
|
|
11
12
|
import { toOpenCollectionAuth } from '../common/auth';
|
|
12
13
|
import { toOpenCollectionHttpHeaders, toOpenCollectionResponseHeaders } from '../common/headers';
|
|
13
14
|
import { toOpenCollectionParams } from '../common/params';
|
|
@@ -17,6 +18,7 @@ import { toOpenCollectionActions } from '../common/actions';
|
|
|
17
18
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
18
19
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
19
20
|
import { isNumber, isNonEmptyString } from '../../../utils';
|
|
21
|
+
import { resolveTimeoutSetting } from '@usebruno/common/utils';
|
|
20
22
|
|
|
21
23
|
const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
22
24
|
try {
|
|
@@ -34,6 +36,9 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
34
36
|
if (item.tags?.length) {
|
|
35
37
|
info.tags = item.tags;
|
|
36
38
|
}
|
|
39
|
+
if (isNonEmptyString(item.description)) {
|
|
40
|
+
info.description = item.description;
|
|
41
|
+
}
|
|
37
42
|
ocRequest.info = info;
|
|
38
43
|
|
|
39
44
|
// http block
|
|
@@ -116,12 +121,7 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
116
121
|
settings.encodeUrl = true;
|
|
117
122
|
}
|
|
118
123
|
|
|
119
|
-
|
|
120
|
-
if (isNumber(timeout)) {
|
|
121
|
-
settings.timeout = timeout;
|
|
122
|
-
} else {
|
|
123
|
-
settings.timeout = 0;
|
|
124
|
-
}
|
|
124
|
+
settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
|
|
125
125
|
|
|
126
126
|
if (httpSettings?.followRedirects === true) {
|
|
127
127
|
settings.followRedirects = true;
|
|
@@ -138,6 +138,8 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
138
138
|
settings.maxRedirects = 5;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
|
|
142
|
+
|
|
141
143
|
ocRequest.settings = settings;
|
|
142
144
|
|
|
143
145
|
// examples
|
|
@@ -212,6 +214,12 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
212
214
|
ocRequest.docs = brunoRequest.docs;
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
// app
|
|
218
|
+
const app: OpenCollectionApp | undefined = toOpenCollectionApp(item.app);
|
|
219
|
+
if (app) {
|
|
220
|
+
(ocRequest as any).app = app;
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
return stringifyYml(ocRequest);
|
|
216
224
|
} catch (error) {
|
|
217
225
|
console.error('Error stringifying HTTP request:', error);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
|
|
2
2
|
import type { WebSocketRequest as BrunoWebSocketRequest } from '@usebruno/schema-types/requests/websocket';
|
|
3
|
-
import type { WebSocketRequest,
|
|
3
|
+
import type { WebSocketRequest, WebSocketRequestInfo, WebSocketRequestDetails, WebSocketRequestRuntime, WebSocketMessage, WebSocketMessageVariant } from '@opencollection/types/requests/websocket';
|
|
4
4
|
import type { Auth } from '@opencollection/types/common/auth';
|
|
5
5
|
import type { Scripts } from '@opencollection/types/common/scripts';
|
|
6
6
|
import type { Variable } from '@opencollection/types/common/variables';
|
|
@@ -28,6 +28,9 @@ const stringifyWebsocketRequest = (item: BrunoItem): string => {
|
|
|
28
28
|
if (item.tags?.length) {
|
|
29
29
|
info.tags = item.tags;
|
|
30
30
|
}
|
|
31
|
+
if (isNonEmptyString(item.description)) {
|
|
32
|
+
info.description = item.description;
|
|
33
|
+
}
|
|
31
34
|
ocRequest.info = info;
|
|
32
35
|
|
|
33
36
|
// websocket block
|
|
@@ -41,21 +44,31 @@ const stringifyWebsocketRequest = (item: BrunoItem): string => {
|
|
|
41
44
|
websocket.headers = headers;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
// message
|
|
47
|
+
// message: single message without a custom name uses flat WebSocketMessage (backward compatible),
|
|
48
|
+
// otherwise uses WebSocketMessageVariant[] to preserve names
|
|
45
49
|
if (brunoRequest.body?.mode === 'ws' && brunoRequest.body.ws?.length) {
|
|
46
50
|
const messages = brunoRequest.body.ws;
|
|
51
|
+
const hasCustomName = messages.length === 1 && messages[0].name && messages[0].name.trim().length > 0;
|
|
52
|
+
|
|
53
|
+
const hasContent = messages.length === 1 && (messages[0].content || '').trim().length > 0;
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
// update this when bruno app supports multiple messages
|
|
50
|
-
if (messages.length) {
|
|
55
|
+
if (messages.length === 1 && !hasCustomName && hasContent) {
|
|
51
56
|
const msg = messages[0];
|
|
52
57
|
const message: WebSocketMessage = {
|
|
53
|
-
type: (msg.type as '
|
|
58
|
+
type: (msg.type as WebSocketMessage['type']) || 'text',
|
|
54
59
|
data: msg.content || ''
|
|
55
60
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
websocket.message = message;
|
|
62
|
+
} else {
|
|
63
|
+
const variants: WebSocketMessageVariant[] = messages.map((msg, index) => ({
|
|
64
|
+
title: msg.name || `message ${index + 1}`,
|
|
65
|
+
selected: msg.selected || false,
|
|
66
|
+
message: {
|
|
67
|
+
type: (msg.type as WebSocketMessage['type']) || 'text',
|
|
68
|
+
data: msg.content || ''
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
websocket.message = variants;
|
|
59
72
|
}
|
|
60
73
|
}
|
|
61
74
|
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import parseCollection from './parseCollection';
|
|
2
|
+
|
|
3
|
+
// Typed `request.variables` propagate through parseCollection into the
|
|
4
|
+
// collectionRoot's vars.req with their dataType + coerced value.
|
|
5
|
+
|
|
6
|
+
describe('parseCollection — typed request.variables', () => {
|
|
7
|
+
it('coerces typed values, preserves raw value on un-coercible input, and treats explicit string as the implicit default', () => {
|
|
8
|
+
const yml = `opencollection: "1.0.0"
|
|
9
|
+
info:
|
|
10
|
+
name: c
|
|
11
|
+
|
|
12
|
+
request:
|
|
13
|
+
variables:
|
|
14
|
+
- name: count
|
|
15
|
+
value:
|
|
16
|
+
type: number
|
|
17
|
+
data: "42"
|
|
18
|
+
- name: enabled
|
|
19
|
+
value:
|
|
20
|
+
type: boolean
|
|
21
|
+
data: "true"
|
|
22
|
+
- name: config
|
|
23
|
+
value:
|
|
24
|
+
type: object
|
|
25
|
+
data: '{"a":1}'
|
|
26
|
+
- name: greeting
|
|
27
|
+
value:
|
|
28
|
+
type: string
|
|
29
|
+
data: hi
|
|
30
|
+
- name: plain
|
|
31
|
+
value: hello
|
|
32
|
+
- name: cfg
|
|
33
|
+
value:
|
|
34
|
+
type: object
|
|
35
|
+
data: 'not-json'
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const { collectionRoot } = parseCollection(yml);
|
|
39
|
+
const reqVars = collectionRoot.request!.vars!.req!;
|
|
40
|
+
|
|
41
|
+
expect(reqVars).toHaveLength(6);
|
|
42
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
43
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
44
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
45
|
+
// Explicit `type: string` is the implicit default — no dataType materialized.
|
|
46
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
47
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
48
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
49
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
50
|
+
// Un-coercible: raw value preserved for the UI mismatch warning.
|
|
51
|
+
expect(reqVars[5]).toMatchObject({ name: 'cfg', value: 'not-json', dataType: 'object' });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
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
|
+
|
|
178
|
+
describe('parseCollection — reading the collection version', () => {
|
|
179
|
+
it('reads the version from the file as text', () => {
|
|
180
|
+
const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n version: v1.0.0\n');
|
|
181
|
+
expect(brunoConfig.version).toBe('v1.0.0');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('turns a number version into text (2 becomes "2")', () => {
|
|
185
|
+
const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n version: 2\n');
|
|
186
|
+
expect(brunoConfig.version).toBe('2');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('has no version when the file does not have one', () => {
|
|
190
|
+
const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n');
|
|
191
|
+
expect(brunoConfig.version).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -7,6 +7,7 @@ import { toBrunoVariables } from './common/variables';
|
|
|
7
7
|
import { toBrunoPostResponseVariables } from './common/actions';
|
|
8
8
|
import { toBrunoScripts } from './common/scripts';
|
|
9
9
|
import { ensureString } from '../../utils';
|
|
10
|
+
import type { BrunoPresetsExtension } from '../../types';
|
|
10
11
|
|
|
11
12
|
interface ParsedCollection {
|
|
12
13
|
collectionRoot: FolderRoot;
|
|
@@ -25,6 +26,10 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
25
26
|
ignore: []
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
if (oc.info?.version != null && oc.info.version !== '') {
|
|
30
|
+
brunoConfig.version = ensureString(oc.info.version, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
const brunoExtension = (oc.extensions as any)?.bruno;
|
|
29
34
|
if (brunoExtension?.ignore && Array.isArray(brunoExtension.ignore)) {
|
|
30
35
|
brunoConfig.ignore = brunoExtension.ignore;
|
|
@@ -32,12 +37,15 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
32
37
|
|
|
33
38
|
// presets
|
|
34
39
|
if (brunoExtension?.presets) {
|
|
35
|
-
const presets = brunoExtension.presets as
|
|
36
|
-
if (presets.request) {
|
|
40
|
+
const presets = brunoExtension.presets as BrunoPresetsExtension;
|
|
41
|
+
if (presets.request || presets.defaultEnvironment) {
|
|
37
42
|
brunoConfig.presets = {
|
|
38
|
-
requestType: presets.request
|
|
39
|
-
requestUrl: presets.request
|
|
43
|
+
requestType: presets.request?.type || '',
|
|
44
|
+
requestUrl: presets.request?.url || ''
|
|
40
45
|
};
|
|
46
|
+
if (presets.defaultEnvironment) {
|
|
47
|
+
brunoConfig.presets.defaultEnvironment = presets.defaultEnvironment;
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -54,6 +62,14 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
54
62
|
};
|
|
55
63
|
}
|
|
56
64
|
}
|
|
65
|
+
// scripts.flow controls collection/folder/request script execution order
|
|
66
|
+
// ('sandwich' | 'sequential'); unrecognized values fall back to the runtime default.
|
|
67
|
+
if (brunoExtensions?.scripts?.flow === 'sandwich' || brunoExtensions?.scripts?.flow === 'sequential') {
|
|
68
|
+
brunoConfig.scripts = {
|
|
69
|
+
...brunoConfig.scripts,
|
|
70
|
+
flow: brunoExtensions.scripts.flow
|
|
71
|
+
};
|
|
72
|
+
}
|
|
57
73
|
if (Array.isArray(brunoExtensions?.openapi) && brunoExtensions.openapi.length > 0) {
|
|
58
74
|
brunoConfig.openapi = brunoExtensions.openapi.map((entry: any) => ({
|
|
59
75
|
sourceUrl: entry.sourceUrl,
|
|
@@ -137,14 +153,16 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
137
153
|
type: 'cert',
|
|
138
154
|
certFilePath: cert.certificateFilePath,
|
|
139
155
|
keyFilePath: cert.privateKeyFilePath,
|
|
140
|
-
passphrase: cert.passphrase || ''
|
|
156
|
+
passphrase: cert.passphrase || '',
|
|
157
|
+
...(cert.disabled === true && { disabled: true })
|
|
141
158
|
};
|
|
142
159
|
} else if (cert.type === 'pkcs12') {
|
|
143
160
|
return {
|
|
144
161
|
domain: cert.domain,
|
|
145
162
|
type: 'pfx',
|
|
146
163
|
pfxFilePath: cert.pkcs12FilePath,
|
|
147
|
-
passphrase: cert.passphrase || ''
|
|
164
|
+
passphrase: cert.passphrase || '',
|
|
165
|
+
...(cert.disabled === true && { disabled: true })
|
|
148
166
|
};
|
|
149
167
|
}
|
|
150
168
|
return null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import parseEnvironment, { toBrunoEnvironmentVariables } from './parseEnvironment';
|
|
2
|
+
|
|
3
|
+
describe('toBrunoEnvironmentVariables', () => {
|
|
4
|
+
it('returns an empty array for null / empty input', () => {
|
|
5
|
+
expect(toBrunoEnvironmentVariables(null)).toEqual([]);
|
|
6
|
+
expect(toBrunoEnvironmentVariables(undefined)).toEqual([]);
|
|
7
|
+
expect(toBrunoEnvironmentVariables([])).toEqual([]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('parses a plain string variable as text with secret=false', () => {
|
|
11
|
+
const [variable] = toBrunoEnvironmentVariables([{ name: 'apiKey', value: 'abc' } as any]);
|
|
12
|
+
|
|
13
|
+
expect(variable).toMatchObject({
|
|
14
|
+
name: 'apiKey',
|
|
15
|
+
value: 'abc',
|
|
16
|
+
type: 'text',
|
|
17
|
+
enabled: true,
|
|
18
|
+
secret: false
|
|
19
|
+
});
|
|
20
|
+
expect(variable.uid).toEqual(expect.any(String));
|
|
21
|
+
expect(variable.dataType).toBeUndefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('parses typed values and attaches the dataType field', () => {
|
|
25
|
+
const vars = toBrunoEnvironmentVariables([
|
|
26
|
+
{ name: 'port', value: { type: 'number', data: '300' } } as any,
|
|
27
|
+
{ name: 'flag', value: { type: 'boolean', data: 'true' } } as any,
|
|
28
|
+
{ name: 'config', value: { type: 'object', data: '{"a":1}' } } as any
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
expect(vars[0]).toMatchObject({ name: 'port', value: 300, dataType: 'number', secret: false });
|
|
32
|
+
expect(vars[1]).toMatchObject({ name: 'flag', value: true, dataType: 'boolean', secret: false });
|
|
33
|
+
expect(vars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object', secret: false });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('parses secret variables with secret=true and attaches the dataType from `type`', () => {
|
|
37
|
+
const vars = toBrunoEnvironmentVariables([
|
|
38
|
+
{ secret: true, name: 'apiKey' } as any,
|
|
39
|
+
{ secret: true, name: 'port', type: 'number' } as any,
|
|
40
|
+
{ secret: true, name: 'flag', type: 'boolean', disabled: true } as any
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
expect(vars[0]).toMatchObject({ name: 'apiKey', secret: true, enabled: true, value: '' });
|
|
44
|
+
// A bare secret has no `type`, so no dataType materializes.
|
|
45
|
+
expect(vars[0].dataType).toBeUndefined();
|
|
46
|
+
expect(vars[1]).toMatchObject({ name: 'port', secret: true, enabled: true, dataType: 'number' });
|
|
47
|
+
expect(vars[2]).toMatchObject({ name: 'flag', secret: true, enabled: false, dataType: 'boolean' });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('honors the disabled flag on non-secret variables', () => {
|
|
51
|
+
const [variable] = toBrunoEnvironmentVariables([
|
|
52
|
+
{ name: 'apiKey', value: 'abc', disabled: true } as any
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(variable.enabled).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('parseEnvironment (full yml)', () => {
|
|
60
|
+
it('parses an environment with mixed plain, typed and secret variables', () => {
|
|
61
|
+
const yml = `name: test_env
|
|
62
|
+
variables:
|
|
63
|
+
- name: env_str
|
|
64
|
+
value: hello
|
|
65
|
+
- name: env_num
|
|
66
|
+
value:
|
|
67
|
+
type: number
|
|
68
|
+
data: "300"
|
|
69
|
+
- name: env_bool
|
|
70
|
+
value:
|
|
71
|
+
type: boolean
|
|
72
|
+
data: "true"
|
|
73
|
+
- name: env_obj
|
|
74
|
+
value:
|
|
75
|
+
type: object
|
|
76
|
+
data: '{"scope":"env"}'
|
|
77
|
+
- secret: true
|
|
78
|
+
name: env_secret
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
const env = parseEnvironment(yml);
|
|
82
|
+
|
|
83
|
+
expect(env.name).toBe('test_env');
|
|
84
|
+
expect(env.variables).toHaveLength(5);
|
|
85
|
+
|
|
86
|
+
expect(env.variables[0]).toMatchObject({ name: 'env_str', value: 'hello', secret: false });
|
|
87
|
+
expect(env.variables[1]).toMatchObject({ name: 'env_num', value: 300, dataType: 'number', secret: false });
|
|
88
|
+
expect(env.variables[2]).toMatchObject({ name: 'env_bool', value: true, dataType: 'boolean', secret: false });
|
|
89
|
+
expect(env.variables[3]).toMatchObject({ name: 'env_obj', value: { scope: 'env' }, dataType: 'object', secret: false });
|
|
90
|
+
expect(env.variables[4]).toMatchObject({ name: 'env_secret', secret: true, value: '' });
|
|
91
|
+
expect(env.variables[4].dataType).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('defaults environment name when missing', () => {
|
|
95
|
+
const env = parseEnvironment('variables: []');
|
|
96
|
+
expect(env.name).toBe('Untitled Environment');
|
|
97
|
+
expect(env.variables).toEqual([]);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Environment as BrunoEnvironment,
|
|
3
|
+
EnvironmentVariable as BrunoEnvironmentVariable,
|
|
4
|
+
SecretEnvironmentVariable as BrunoSecretEnvironmentVariable,
|
|
5
|
+
ExternalSecretProviderType as BrunoExternalSecretProviderType
|
|
6
|
+
} from '@usebruno/schema-types/collection/environment';
|
|
2
7
|
import type { Environment } from '@opencollection/types/config/environments';
|
|
3
8
|
import type { Variable, SecretVariable } from '@opencollection/types/common/variables';
|
|
4
9
|
import { parseYml } from './utils';
|
|
5
10
|
import { uuid, ensureString } from '../../utils';
|
|
6
|
-
import {
|
|
11
|
+
import { SECRET_PROVIDER_IDENTIFIER_FIELDS } from '../../constants';
|
|
12
|
+
import { isTypedValue, fromOpenCollectionTypedValue } from './common/datatype';
|
|
7
13
|
|
|
8
14
|
const isSecretVariable = (v: Variable | SecretVariable): v is SecretVariable => {
|
|
9
15
|
return 'secret' in v && v.secret === true;
|
|
10
16
|
};
|
|
11
17
|
|
|
12
|
-
const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] | null | undefined): BrunoEnvironmentVariable[] => {
|
|
18
|
+
export const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] | null | undefined): (BrunoEnvironmentVariable | BrunoSecretEnvironmentVariable)[] => {
|
|
13
19
|
if (!variables?.length) {
|
|
14
20
|
return [];
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
return variables.map((v): BrunoEnvironmentVariable => {
|
|
23
|
+
return variables.map((v): (BrunoEnvironmentVariable | BrunoSecretEnvironmentVariable) => {
|
|
18
24
|
if (isSecretVariable(v)) {
|
|
19
25
|
const variable: BrunoEnvironmentVariable = {
|
|
20
26
|
uid: uuid(),
|
|
@@ -26,11 +32,16 @@ const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] |
|
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
if (v.type && v.type !== 'string' && v.type !== 'null') {
|
|
29
|
-
variable.
|
|
35
|
+
variable.dataType = v.type;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (v.description) {
|
|
39
|
+
variable.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || '';
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
return variable;
|
|
33
43
|
}
|
|
44
|
+
|
|
34
45
|
const variable: BrunoEnvironmentVariable = {
|
|
35
46
|
uid: uuid(),
|
|
36
47
|
name: ensureString(v.name),
|
|
@@ -41,36 +52,47 @@ const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] |
|
|
|
41
52
|
};
|
|
42
53
|
|
|
43
54
|
if (isTypedValue(v.value)) {
|
|
44
|
-
variable
|
|
45
|
-
if (v.value.type !== 'string' && v.value.type !== 'null') {
|
|
46
|
-
variable.datatype = v.value.type;
|
|
47
|
-
}
|
|
55
|
+
Object.assign(variable, fromOpenCollectionTypedValue(v.value));
|
|
48
56
|
} else {
|
|
49
57
|
variable.value = ensureString(v.value);
|
|
50
58
|
}
|
|
51
59
|
|
|
60
|
+
if (v.description) {
|
|
61
|
+
variable.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || '';
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
return variable;
|
|
53
65
|
});
|
|
54
66
|
};
|
|
55
67
|
|
|
68
|
+
// Parses the yml `externalSecrets: { type, variables: [{ name, path|secretName|vaultName }] }`
|
|
69
|
+
// block into the runtime shape. `uid` and `enabled` are added here; the fetched `value`,
|
|
70
|
+
// `providerConfig` and `lastSyncedAt` are runtime-only and never present in the file.
|
|
56
71
|
const toBrunoExternalSecrets = (externalSecrets: any): BrunoEnvironment['externalSecrets'] | undefined => {
|
|
57
72
|
if (!externalSecrets || typeof externalSecrets !== 'object') {
|
|
58
73
|
return undefined;
|
|
59
74
|
}
|
|
60
75
|
|
|
76
|
+
const type = ensureString(externalSecrets.type) as BrunoExternalSecretProviderType;
|
|
77
|
+
const identifierField = SECRET_PROVIDER_IDENTIFIER_FIELDS[type];
|
|
78
|
+
if (!identifierField) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Keep any named entry, even one with an empty identifier — a half-filled secret row is the user's
|
|
83
|
+
// data; dropping it here would silently delete it on the next save.
|
|
61
84
|
const variables = Array.isArray(externalSecrets.variables)
|
|
62
|
-
? externalSecrets.variables
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
})
|
|
85
|
+
? externalSecrets.variables
|
|
86
|
+
.filter((v: any) => v && v.name)
|
|
87
|
+
.map((v: any) => ({
|
|
88
|
+
uid: uuid(),
|
|
89
|
+
name: ensureString(v.name),
|
|
90
|
+
enabled: v.disabled !== true,
|
|
91
|
+
[identifierField]: ensureString(v[identifierField])
|
|
92
|
+
}))
|
|
71
93
|
: [];
|
|
72
94
|
|
|
73
|
-
return { type
|
|
95
|
+
return { type, variables } as BrunoEnvironment['externalSecrets'];
|
|
74
96
|
};
|
|
75
97
|
|
|
76
98
|
const parseEnvironment = (ymlString: string): BrunoEnvironment => {
|