@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import parseFolder from './parseFolder';
|
|
2
|
+
|
|
3
|
+
// Typed `request.variables` propagate through parseFolder into the folder's
|
|
4
|
+
// vars.req with their dataType + coerced value.
|
|
5
|
+
|
|
6
|
+
describe('parseFolder — 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 = `info:
|
|
9
|
+
name: my-folder
|
|
10
|
+
|
|
11
|
+
request:
|
|
12
|
+
variables:
|
|
13
|
+
- name: count
|
|
14
|
+
value:
|
|
15
|
+
type: number
|
|
16
|
+
data: "42"
|
|
17
|
+
- name: enabled
|
|
18
|
+
value:
|
|
19
|
+
type: boolean
|
|
20
|
+
data: "true"
|
|
21
|
+
- name: config
|
|
22
|
+
value:
|
|
23
|
+
type: object
|
|
24
|
+
data: '{"a":1}'
|
|
25
|
+
- name: greeting
|
|
26
|
+
value:
|
|
27
|
+
type: string
|
|
28
|
+
data: hi
|
|
29
|
+
- name: plain
|
|
30
|
+
value: hello
|
|
31
|
+
- name: flag
|
|
32
|
+
value:
|
|
33
|
+
type: boolean
|
|
34
|
+
data: 'maybe'
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const folder = parseFolder(yml);
|
|
38
|
+
const reqVars = folder.request!.vars!.req!;
|
|
39
|
+
|
|
40
|
+
expect(reqVars).toHaveLength(6);
|
|
41
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
42
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
43
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
44
|
+
// Explicit `type: string` is the implicit default — no dataType materialized.
|
|
45
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
46
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
47
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
48
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
49
|
+
// Un-coercible: raw value preserved for the UI mismatch warning.
|
|
50
|
+
expect(reqVars[5]).toMatchObject({ name: 'flag', value: 'maybe', dataType: 'boolean' });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('parseFolder — seq', () => {
|
|
55
|
+
it('leaves seq undefined when folder.yml has no seq field', () => {
|
|
56
|
+
const yml = `info:\n name: my-folder\n`;
|
|
57
|
+
const { meta } = parseFolder(yml);
|
|
58
|
+
|
|
59
|
+
expect(meta).toEqual(expect.objectContaining({ name: 'my-folder' }));
|
|
60
|
+
expect(meta!.seq).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('preserves an explicit numeric seq from the file', () => {
|
|
64
|
+
const yml = `info:\n name: my-folder\n seq: 3\n`;
|
|
65
|
+
const { meta } = parseFolder(yml);
|
|
66
|
+
|
|
67
|
+
expect(meta).toEqual(expect.objectContaining({ name: 'my-folder' }));
|
|
68
|
+
expect(meta!.seq).toBe(3);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -13,21 +13,19 @@ const parseFolder = (ymlString: string): FolderRoot => {
|
|
|
13
13
|
const ocFolder: Folder = parseYml(ymlString);
|
|
14
14
|
|
|
15
15
|
const info = ocFolder.info;
|
|
16
|
+
const seq = info?.seq;
|
|
16
17
|
|
|
17
18
|
const folderRoot: FolderRoot = {
|
|
18
19
|
meta: {
|
|
19
20
|
name: ensureString(info?.name, 'Untitled Folder'),
|
|
20
|
-
seq
|
|
21
|
+
// Only set seq when the source has a numeric value. Missing seq must stay absent:
|
|
22
|
+
// defaulting to 1 makes every seq-less folder look "ordered at position 1" to
|
|
23
|
+
// sortByNameThenSequence, pinning them all to slot 1 instead of alphabetical sort.
|
|
24
|
+
...(typeof seq === 'number' && Number.isFinite(seq) ? { seq } : {})
|
|
21
25
|
},
|
|
22
|
-
request:
|
|
23
|
-
docs: null
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// request defaults
|
|
27
|
-
if (ocFolder.request) {
|
|
28
|
-
folderRoot.request = {
|
|
26
|
+
request: {
|
|
29
27
|
headers: [],
|
|
30
|
-
auth:
|
|
28
|
+
auth: toBrunoAuth(ocFolder.request?.auth),
|
|
31
29
|
script: {
|
|
32
30
|
req: null,
|
|
33
31
|
res: null
|
|
@@ -37,40 +35,39 @@ const parseFolder = (ymlString: string): FolderRoot => {
|
|
|
37
35
|
res: []
|
|
38
36
|
},
|
|
39
37
|
tests: null
|
|
40
|
-
}
|
|
38
|
+
},
|
|
39
|
+
docs: null
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (ocFolder.request) {
|
|
43
|
+
const folderRequest = folderRoot.request!;
|
|
41
44
|
|
|
42
45
|
// headers
|
|
43
46
|
const headers = toBrunoHttpHeaders(ocFolder.request.headers);
|
|
44
47
|
if (headers) {
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// auth
|
|
49
|
-
const auth = toBrunoAuth(ocFolder.request.auth);
|
|
50
|
-
if (auth) {
|
|
51
|
-
folderRoot.request.auth = auth;
|
|
48
|
+
folderRequest.headers = headers;
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
// variables
|
|
55
52
|
const variables = toBrunoVariables(ocFolder.request.variables);
|
|
56
53
|
const postResponseVars = toBrunoPostResponseVariables((ocFolder.request as any).actions);
|
|
57
|
-
|
|
54
|
+
folderRequest.vars = {
|
|
58
55
|
req: variables.req,
|
|
59
56
|
res: postResponseVars
|
|
60
57
|
};
|
|
61
58
|
|
|
62
59
|
// scripts
|
|
63
60
|
const scripts = toBrunoScripts(ocFolder.request.scripts);
|
|
64
|
-
if (scripts?.script &&
|
|
61
|
+
if (scripts?.script && folderRequest.script) {
|
|
65
62
|
if (scripts.script.req) {
|
|
66
|
-
|
|
63
|
+
folderRequest.script.req = scripts.script.req;
|
|
67
64
|
}
|
|
68
65
|
if (scripts.script.res) {
|
|
69
|
-
|
|
66
|
+
folderRequest.script.res = scripts.script.res;
|
|
70
67
|
}
|
|
71
68
|
}
|
|
72
69
|
if (scripts?.tests) {
|
|
73
|
-
|
|
70
|
+
folderRequest.tests = scripts.tests;
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import parseItem from './parseItem';
|
|
2
|
+
|
|
3
|
+
// Typed `runtime.variables` propagate through parseItem with their dataType
|
|
4
|
+
// + coerced value. Helper-level coverage lives in the variables/datatype specs.
|
|
5
|
+
|
|
6
|
+
describe('parseItem — typed runtime.variables', () => {
|
|
7
|
+
it('coerces typed values, preserves raw value on un-coercible input, and treats explicit string as the implicit default', () => {
|
|
8
|
+
const yml = `info:
|
|
9
|
+
name: r
|
|
10
|
+
type: http
|
|
11
|
+
|
|
12
|
+
http:
|
|
13
|
+
url: https://example.com
|
|
14
|
+
method: GET
|
|
15
|
+
|
|
16
|
+
runtime:
|
|
17
|
+
variables:
|
|
18
|
+
- name: count
|
|
19
|
+
value:
|
|
20
|
+
type: number
|
|
21
|
+
data: "42"
|
|
22
|
+
- name: enabled
|
|
23
|
+
value:
|
|
24
|
+
type: boolean
|
|
25
|
+
data: "true"
|
|
26
|
+
- name: config
|
|
27
|
+
value:
|
|
28
|
+
type: object
|
|
29
|
+
data: '{"a":1}'
|
|
30
|
+
- name: greeting
|
|
31
|
+
value:
|
|
32
|
+
type: string
|
|
33
|
+
data: hi
|
|
34
|
+
- name: plain
|
|
35
|
+
value: hello
|
|
36
|
+
- name: bad
|
|
37
|
+
value:
|
|
38
|
+
type: number
|
|
39
|
+
data: 'not-a-number'
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const item = parseItem(yml);
|
|
43
|
+
const reqVars = item.request!.vars!.req!;
|
|
44
|
+
|
|
45
|
+
expect(reqVars).toHaveLength(6);
|
|
46
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
47
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
48
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
49
|
+
// Explicit `type: string` is the implicit default — no dataType materialized.
|
|
50
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
51
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
52
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
53
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
54
|
+
// Un-coercible: raw value preserved for the UI mismatch warning.
|
|
55
|
+
expect(reqVars[5]).toMatchObject({ name: 'bad', value: 'not-a-number', dataType: 'number' });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('normalizes raw YAML scalars in `data` to a string before coercing', () => {
|
|
59
|
+
// YAML parses unquoted scalars per their natural JS type. The `String(... ?? '')`
|
|
60
|
+
// cast in fromOpenCollectionTypedValue keeps coercion consistent.
|
|
61
|
+
const yml = `info:
|
|
62
|
+
name: r
|
|
63
|
+
type: http
|
|
64
|
+
|
|
65
|
+
http:
|
|
66
|
+
url: https://example.com
|
|
67
|
+
method: GET
|
|
68
|
+
|
|
69
|
+
runtime:
|
|
70
|
+
variables:
|
|
71
|
+
- name: stringy
|
|
72
|
+
value:
|
|
73
|
+
type: string
|
|
74
|
+
data: 42
|
|
75
|
+
- name: numeric
|
|
76
|
+
value:
|
|
77
|
+
type: number
|
|
78
|
+
data: 42
|
|
79
|
+
- name: nullish
|
|
80
|
+
value:
|
|
81
|
+
type: number
|
|
82
|
+
data: null
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const item = parseItem(yml);
|
|
86
|
+
const reqVars = item.request!.vars!.req!;
|
|
87
|
+
|
|
88
|
+
// type=string: raw YAML number → string; no dataType field.
|
|
89
|
+
expect(reqVars[0]).toMatchObject({ name: 'stringy', value: '42' });
|
|
90
|
+
expect(reqVars[0].dataType).toBeUndefined();
|
|
91
|
+
// type=number: '42' → 42.
|
|
92
|
+
expect(reqVars[1]).toMatchObject({ name: 'numeric', value: 42, dataType: 'number' });
|
|
93
|
+
// data: null → '' (the `?? ''` arm); coerce bails on empty → raw ''.
|
|
94
|
+
expect(reqVars[2]).toMatchObject({ name: 'nullish', value: '', dataType: 'number' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('propagates typed variables for graphql/grpc/websocket requests too', () => {
|
|
98
|
+
const graphqlYml = `info:
|
|
99
|
+
name: g
|
|
100
|
+
type: graphql
|
|
101
|
+
|
|
102
|
+
graphql:
|
|
103
|
+
url: https://example.com/graphql
|
|
104
|
+
method: POST
|
|
105
|
+
|
|
106
|
+
runtime:
|
|
107
|
+
variables:
|
|
108
|
+
- name: count
|
|
109
|
+
value: { type: number, data: '7' }
|
|
110
|
+
`;
|
|
111
|
+
expect(parseItem(graphqlYml).request!.vars!.req![0]).toMatchObject({
|
|
112
|
+
name: 'count', value: 7, dataType: 'number'
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const grpcYml = `info:
|
|
116
|
+
name: gr
|
|
117
|
+
type: grpc
|
|
118
|
+
|
|
119
|
+
grpc:
|
|
120
|
+
url: localhost:50051
|
|
121
|
+
|
|
122
|
+
runtime:
|
|
123
|
+
variables:
|
|
124
|
+
- name: flag
|
|
125
|
+
value: { type: boolean, data: 'false' }
|
|
126
|
+
`;
|
|
127
|
+
expect(parseItem(grpcYml).request!.vars!.req![0]).toMatchObject({
|
|
128
|
+
name: 'flag', value: false, dataType: 'boolean'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const wsYml = `info:
|
|
132
|
+
name: ws
|
|
133
|
+
type: websocket
|
|
134
|
+
|
|
135
|
+
websocket:
|
|
136
|
+
url: wss://example.com/socket
|
|
137
|
+
|
|
138
|
+
runtime:
|
|
139
|
+
variables:
|
|
140
|
+
- name: payload
|
|
141
|
+
value: { type: object, data: '{"k":1}' }
|
|
142
|
+
`;
|
|
143
|
+
expect(parseItem(wsYml).request!.vars!.req![0]).toMatchObject({
|
|
144
|
+
name: 'payload', value: { k: 1 }, dataType: 'object'
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -10,6 +10,7 @@ import parseGraphQLRequest from './items/parseGraphQLRequest';
|
|
|
10
10
|
import parseGrpcRequest from './items/parseGrpcRequest';
|
|
11
11
|
import parseWebsocketRequest from './items/parseWebsocketRequest';
|
|
12
12
|
import parseScript from './items/parseScript';
|
|
13
|
+
import parseApp, { type AppFile } from './items/parseApp';
|
|
13
14
|
|
|
14
15
|
// Helper to get the type from an item (now in info block)
|
|
15
16
|
const getItemType = (item: Item): string | undefined => {
|
|
@@ -87,6 +88,9 @@ const parseItem = (ymlString: string): BrunoItem => {
|
|
|
87
88
|
case 'script':
|
|
88
89
|
return parseScript(ocItem as ScriptFile);
|
|
89
90
|
|
|
91
|
+
case 'app':
|
|
92
|
+
return parseApp(ocItem as unknown as AppFile);
|
|
93
|
+
|
|
90
94
|
case 'folder':
|
|
91
95
|
throw new Error('Folder items should be handled separately using parseFolder');
|
|
92
96
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import parseMockServer from './parseMockServer';
|
|
2
|
+
|
|
3
|
+
describe('parseMockServer', () => {
|
|
4
|
+
it('parses a full mock server file', () => {
|
|
5
|
+
const content = `
|
|
6
|
+
info:
|
|
7
|
+
name: Dog API Mock
|
|
8
|
+
type: mock
|
|
9
|
+
|
|
10
|
+
mock:
|
|
11
|
+
port: 4001
|
|
12
|
+
delay: 250
|
|
13
|
+
source:
|
|
14
|
+
type: collection
|
|
15
|
+
path: /Users/x/collections/dog-api
|
|
16
|
+
|
|
17
|
+
routes:
|
|
18
|
+
- name: Get user 200
|
|
19
|
+
description: Returns a user
|
|
20
|
+
request:
|
|
21
|
+
method: GET
|
|
22
|
+
url: /users/:id
|
|
23
|
+
headers:
|
|
24
|
+
- name: X-Client
|
|
25
|
+
value: bruno
|
|
26
|
+
response:
|
|
27
|
+
status: 200
|
|
28
|
+
statusText: OK
|
|
29
|
+
headers:
|
|
30
|
+
- name: Content-Type
|
|
31
|
+
value: application/json
|
|
32
|
+
body:
|
|
33
|
+
type: json
|
|
34
|
+
data: |-
|
|
35
|
+
{"id": 1}
|
|
36
|
+
rules:
|
|
37
|
+
operator: OR
|
|
38
|
+
conditions:
|
|
39
|
+
- target: query
|
|
40
|
+
key: token
|
|
41
|
+
operator: equals
|
|
42
|
+
value: abc
|
|
43
|
+
copiedFrom:
|
|
44
|
+
example: Success
|
|
45
|
+
requestPath: /Users/x/collections/dog-api/get-user.yml
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const mockServer = parseMockServer(content);
|
|
49
|
+
|
|
50
|
+
expect(mockServer.name).toBe('Dog API Mock');
|
|
51
|
+
expect(mockServer.port).toBe(4001);
|
|
52
|
+
expect(mockServer.delay).toBe(250);
|
|
53
|
+
expect(mockServer.source).toEqual({ type: 'collection', path: '/Users/x/collections/dog-api' });
|
|
54
|
+
|
|
55
|
+
expect(mockServer.routes).toHaveLength(1);
|
|
56
|
+
const route = mockServer.routes[0];
|
|
57
|
+
expect(route.name).toBe('Get user 200');
|
|
58
|
+
expect(route.request.method).toBe('GET');
|
|
59
|
+
expect(route.request.url).toBe('/users/:id');
|
|
60
|
+
expect(route.request.headers[0]).toEqual(
|
|
61
|
+
expect.objectContaining({ name: 'X-Client', value: 'bruno', enabled: true })
|
|
62
|
+
);
|
|
63
|
+
expect(route.request.headers[0].uid).toBeTruthy();
|
|
64
|
+
expect(route.response.status).toBe(200);
|
|
65
|
+
expect(route.response.body).toEqual({ type: 'json', content: '{"id": 1}' });
|
|
66
|
+
expect(route.rules.operator).toBe('OR');
|
|
67
|
+
expect(route.rules.conditions).toEqual([
|
|
68
|
+
{ target: 'query', key: 'token', operator: 'equals', value: 'abc' }
|
|
69
|
+
]);
|
|
70
|
+
expect(route.copiedFrom).toEqual({
|
|
71
|
+
exampleName: 'Success',
|
|
72
|
+
requestPathname: '/Users/x/collections/dog-api/get-user.yml'
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('applies safe defaults for a minimal file', () => {
|
|
77
|
+
const mockServer = parseMockServer('info:\n name: Tiny Mock\n');
|
|
78
|
+
|
|
79
|
+
expect(mockServer).toEqual({
|
|
80
|
+
name: 'Tiny Mock',
|
|
81
|
+
port: null,
|
|
82
|
+
delay: 0,
|
|
83
|
+
source: null,
|
|
84
|
+
routes: []
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('defaults route request/response blocks that are missing', () => {
|
|
89
|
+
const mockServer = parseMockServer(`
|
|
90
|
+
info:
|
|
91
|
+
name: Mock
|
|
92
|
+
|
|
93
|
+
routes:
|
|
94
|
+
- name: Bare route
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
const route = mockServer.routes[0];
|
|
98
|
+
expect(route.request.url).toBe('/');
|
|
99
|
+
expect(route.request.method).toBe('GET');
|
|
100
|
+
expect(route.request.body.mode).toBe('none');
|
|
101
|
+
expect(route.response.status).toBe(200);
|
|
102
|
+
expect(route.response.body).toEqual({ type: 'json', content: '' });
|
|
103
|
+
expect(route.rules).toEqual({ operator: 'AND', conditions: [] });
|
|
104
|
+
expect(route.copiedFrom).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('ignores a source block without a path', () => {
|
|
108
|
+
const mockServer = parseMockServer(`
|
|
109
|
+
info:
|
|
110
|
+
name: Mock
|
|
111
|
+
|
|
112
|
+
mock:
|
|
113
|
+
source:
|
|
114
|
+
type: collection
|
|
115
|
+
`);
|
|
116
|
+
|
|
117
|
+
expect(mockServer.source).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('rejects non-string source paths', () => {
|
|
121
|
+
const numericPath = parseMockServer(`
|
|
122
|
+
info:
|
|
123
|
+
name: Mock
|
|
124
|
+
|
|
125
|
+
mock:
|
|
126
|
+
source:
|
|
127
|
+
type: collection
|
|
128
|
+
path: 12345
|
|
129
|
+
`);
|
|
130
|
+
expect(numericPath.source).toBeNull();
|
|
131
|
+
|
|
132
|
+
const objectPath = parseMockServer(`
|
|
133
|
+
info:
|
|
134
|
+
name: Mock
|
|
135
|
+
|
|
136
|
+
mock:
|
|
137
|
+
source:
|
|
138
|
+
type: spec
|
|
139
|
+
path:
|
|
140
|
+
nested: value
|
|
141
|
+
`);
|
|
142
|
+
expect(objectPath.source).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('rejects non-string copiedFrom values', () => {
|
|
146
|
+
const mockServer = parseMockServer(`
|
|
147
|
+
info:
|
|
148
|
+
name: Mock
|
|
149
|
+
|
|
150
|
+
routes:
|
|
151
|
+
- name: Numeric example
|
|
152
|
+
copiedFrom:
|
|
153
|
+
example: 42
|
|
154
|
+
requestPath: /real/path.yml
|
|
155
|
+
- name: Object request path
|
|
156
|
+
copiedFrom:
|
|
157
|
+
example: Success
|
|
158
|
+
requestPath:
|
|
159
|
+
nested: value
|
|
160
|
+
- name: Fully malformed
|
|
161
|
+
copiedFrom:
|
|
162
|
+
example: 1
|
|
163
|
+
requestPath: 2
|
|
164
|
+
`);
|
|
165
|
+
|
|
166
|
+
expect(mockServer.routes[0].copiedFrom).toEqual({
|
|
167
|
+
exampleName: null,
|
|
168
|
+
requestPathname: '/real/path.yml'
|
|
169
|
+
});
|
|
170
|
+
expect(mockServer.routes[1].copiedFrom).toEqual({
|
|
171
|
+
exampleName: 'Success',
|
|
172
|
+
requestPathname: null
|
|
173
|
+
});
|
|
174
|
+
expect(mockServer.routes[2].copiedFrom).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('throws on non-object content', () => {
|
|
178
|
+
expect(() => parseMockServer('- a\n- b\n')).toThrow('Invalid mock server file');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { parseYml } from './utils';
|
|
2
|
+
import { ensureString, isNonEmptyString } from '../../utils';
|
|
3
|
+
import { toBrunoHttpHeaders } from './common/headers';
|
|
4
|
+
import { toBrunoParams } from './common/params';
|
|
5
|
+
import { toBrunoBody } from './common/body';
|
|
6
|
+
import type {
|
|
7
|
+
MockServerFile,
|
|
8
|
+
MockRouteEntry,
|
|
9
|
+
BrunoMockServer,
|
|
10
|
+
BrunoMockRoute,
|
|
11
|
+
MockRuleCondition
|
|
12
|
+
} from './mockServerTypes';
|
|
13
|
+
|
|
14
|
+
const emptyRequestBody = () => ({
|
|
15
|
+
mode: 'none',
|
|
16
|
+
json: null,
|
|
17
|
+
text: null,
|
|
18
|
+
xml: null,
|
|
19
|
+
sparql: null,
|
|
20
|
+
formUrlEncoded: null,
|
|
21
|
+
multipartForm: null,
|
|
22
|
+
graphql: null,
|
|
23
|
+
file: null
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const toBrunoRuleConditions = (conditions: MockRuleCondition[] | null | undefined) => {
|
|
27
|
+
if (!Array.isArray(conditions)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return conditions.map((condition) => ({
|
|
32
|
+
target: ensureString(condition?.target),
|
|
33
|
+
key: ensureString(condition?.key),
|
|
34
|
+
operator: ensureString(condition?.operator, 'equals'),
|
|
35
|
+
value: ensureString(condition?.value)
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const toBrunoMockRoute = (route: MockRouteEntry): BrunoMockRoute => {
|
|
40
|
+
const responseStatus = Number(route?.response?.status) || 200;
|
|
41
|
+
const responseStatusText = ensureString(route?.response?.statusText);
|
|
42
|
+
|
|
43
|
+
const brunoRoute: BrunoMockRoute = {
|
|
44
|
+
name: ensureString(route?.name, 'Untitled Mock Response'),
|
|
45
|
+
description: ensureString(route?.description),
|
|
46
|
+
request: {
|
|
47
|
+
url: ensureString(route?.request?.url, '/'),
|
|
48
|
+
method: ensureString(route?.request?.method, 'GET'),
|
|
49
|
+
headers: toBrunoHttpHeaders(route?.request?.headers) || [],
|
|
50
|
+
params: toBrunoParams(route?.request?.params) || [],
|
|
51
|
+
body: toBrunoBody(route?.request?.body) || emptyRequestBody()
|
|
52
|
+
},
|
|
53
|
+
response: {
|
|
54
|
+
status: responseStatus,
|
|
55
|
+
statusText: responseStatusText === 'OK' && responseStatus !== 200 ? '' : responseStatusText,
|
|
56
|
+
headers: toBrunoHttpHeaders(route?.response?.headers) || [],
|
|
57
|
+
body: {
|
|
58
|
+
type: ensureString(route?.response?.body?.type, 'json'),
|
|
59
|
+
content: ensureString(route?.response?.body?.data)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
rules: {
|
|
63
|
+
operator: route?.rules?.operator === 'OR' ? 'OR' : 'AND',
|
|
64
|
+
conditions: toBrunoRuleConditions(route?.rules?.conditions)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const rawExample = route?.copiedFrom?.example;
|
|
69
|
+
const rawRequestPath = route?.copiedFrom?.requestPath;
|
|
70
|
+
const copiedFromExample = isNonEmptyString(rawExample) ? rawExample : null;
|
|
71
|
+
const copiedFromRequestPath = isNonEmptyString(rawRequestPath) ? rawRequestPath : null;
|
|
72
|
+
|
|
73
|
+
if (copiedFromExample || copiedFromRequestPath) {
|
|
74
|
+
brunoRoute.copiedFrom = {
|
|
75
|
+
exampleName: copiedFromExample,
|
|
76
|
+
requestPathname: copiedFromRequestPath
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return brunoRoute;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const parseMockServer = (content: string): BrunoMockServer => {
|
|
84
|
+
const parsed: MockServerFile = parseYml(content);
|
|
85
|
+
|
|
86
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
87
|
+
throw new Error('Invalid mock server file');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const info = parsed.info || ({} as NonNullable<MockServerFile['info']>);
|
|
91
|
+
const mock = parsed.mock || ({} as NonNullable<MockServerFile['mock']>);
|
|
92
|
+
|
|
93
|
+
const brunoMockServer: BrunoMockServer = {
|
|
94
|
+
name: ensureString(info.name, 'Mock Server'),
|
|
95
|
+
port: Number(mock.port) || null,
|
|
96
|
+
delay: Number(mock.delay) || 0,
|
|
97
|
+
source: null,
|
|
98
|
+
routes: Array.isArray(parsed.routes) ? parsed.routes.map(toBrunoMockRoute) : []
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const sourceType = mock.source?.type;
|
|
102
|
+
const sourcePath = mock.source?.path;
|
|
103
|
+
if ((sourceType === 'collection' || sourceType === 'spec') && isNonEmptyString(sourcePath)) {
|
|
104
|
+
brunoMockServer.source = {
|
|
105
|
+
type: sourceType,
|
|
106
|
+
path: sourcePath
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return brunoMockServer;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default parseMockServer;
|