@usebruno/filestore 0.11.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 +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/workers/worker-script.js +1 -1
- package/dist/esm/workers/worker-script.js.map +1 -1
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -1
- package/dist/formats/yml/index.d.ts +3 -1
- package/dist/formats/yml/index.d.ts.map +1 -1
- package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyHttpRequest.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/parseFolder.d.ts.map +1 -1
- package/dist/formats/yml/parseMockServer.d.ts +4 -0
- package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
- package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
- package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
- package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
- package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/workers/WorkerQueue/index.d.ts +3 -2
- package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
- package/dist/workers/index.d.ts +4 -0
- package/dist/workers/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/constants.ts +5 -1
- package/src/formats/bru/tests/redact-large-text-blocks.spec.js +160 -1
- package/src/formats/bru/utils/redact-large-text-blocks.ts +42 -1
- package/src/formats/yml/index.ts +5 -1
- package/src/formats/yml/items/parseGraphQLRequest.ts +6 -16
- package/src/formats/yml/items/parseHttpRequest.ts +6 -16
- package/src/formats/yml/items/stringifyGraphQLRequest.ts +4 -6
- package/src/formats/yml/items/stringifyHttpRequest.ts +4 -6
- package/src/formats/yml/mockServerTypes.ts +95 -0
- package/src/formats/yml/parseCollection.spec.ts +123 -0
- package/src/formats/yml/parseCollection.ts +18 -5
- package/src/formats/yml/parseFolder.spec.ts +18 -0
- package/src/formats/yml/parseFolder.ts +5 -1
- package/src/formats/yml/parseMockServer.spec.ts +180 -0
- package/src/formats/yml/parseMockServer.ts +113 -0
- package/src/formats/yml/stringifyCollection.spec.ts +146 -0
- package/src/formats/yml/stringifyCollection.ts +22 -9
- package/src/formats/yml/stringifyFolder.spec.ts +23 -0
- package/src/formats/yml/stringifyFolder.ts +7 -2
- package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
- package/src/formats/yml/stringifyMockServer.ts +129 -0
- package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
- package/src/formats/yml/tests/stringifyEnvironment.spec.ts +26 -0
- package/src/formats/yml/utils.ts +1 -1
- package/src/index.ts +34 -1
- package/src/types.ts +1 -0
- package/src/workers/WorkerQueue/index.ts +10 -2
- package/src/workers/index.ts +25 -1
- package/src/workers/worker-script.ts +45 -21
|
@@ -11,6 +11,8 @@ import { toBrunoScripts } from '../common/scripts';
|
|
|
11
11
|
import { toBrunoAssertions } from '../common/assertions';
|
|
12
12
|
import { toBrunoApp } from '../common/app';
|
|
13
13
|
import { uuid, ensureString } from '../../../utils';
|
|
14
|
+
import { utils } from '@usebruno/common';
|
|
15
|
+
const { toBool, toNumber } = utils;
|
|
14
16
|
|
|
15
17
|
const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
16
18
|
const info = ocRequest.info;
|
|
@@ -113,11 +115,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
113
115
|
if (ocRequest.settings) {
|
|
114
116
|
const settings: BrunoHttpItemSettings = {};
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
settings.encodeUrl = ocRequest.settings.encodeUrl;
|
|
118
|
-
} else {
|
|
119
|
-
settings.encodeUrl = true;
|
|
120
|
-
}
|
|
118
|
+
settings.encodeUrl = toBool(ocRequest.settings.encodeUrl, true);
|
|
121
119
|
|
|
122
120
|
if (typeof ocRequest.settings.timeout === 'number') {
|
|
123
121
|
settings.timeout = ocRequest.settings.timeout;
|
|
@@ -127,17 +125,9 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
127
125
|
settings.timeout = 0;
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
settings.followRedirects = true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (typeof ocRequest.settings.maxRedirects === 'number') {
|
|
137
|
-
settings.maxRedirects = ocRequest.settings.maxRedirects;
|
|
138
|
-
} else {
|
|
139
|
-
settings.maxRedirects = 5;
|
|
140
|
-
}
|
|
128
|
+
settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
|
|
129
|
+
settings.maxRedirects = toNumber(ocRequest.settings.maxRedirects, 5);
|
|
130
|
+
settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
|
|
141
131
|
|
|
142
132
|
brunoItem.settings = settings;
|
|
143
133
|
}
|
|
@@ -16,6 +16,7 @@ import { toOpenCollectionVariables } from '../common/variables';
|
|
|
16
16
|
import { toOpenCollectionActions } from '../common/actions';
|
|
17
17
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
18
18
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
19
|
+
import { resolveTimeoutSetting } from '@usebruno/common/utils';
|
|
19
20
|
|
|
20
21
|
const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
21
22
|
try {
|
|
@@ -132,12 +133,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
|
132
133
|
settings.encodeUrl = true;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
if (isNumber(timeout)) {
|
|
137
|
-
settings.timeout = timeout;
|
|
138
|
-
} else {
|
|
139
|
-
settings.timeout = 0;
|
|
140
|
-
}
|
|
136
|
+
settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
|
|
141
137
|
|
|
142
138
|
if (httpSettings?.followRedirects === true) {
|
|
143
139
|
settings.followRedirects = true;
|
|
@@ -154,6 +150,8 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
|
154
150
|
settings.maxRedirects = 5;
|
|
155
151
|
}
|
|
156
152
|
|
|
153
|
+
settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
|
|
154
|
+
|
|
157
155
|
ocRequest.settings = settings;
|
|
158
156
|
|
|
159
157
|
// docs
|
|
@@ -18,6 +18,7 @@ import { toOpenCollectionActions } from '../common/actions';
|
|
|
18
18
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
19
19
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
20
20
|
import { isNumber, isNonEmptyString } from '../../../utils';
|
|
21
|
+
import { resolveTimeoutSetting } from '@usebruno/common/utils';
|
|
21
22
|
|
|
22
23
|
const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
23
24
|
try {
|
|
@@ -120,12 +121,7 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
120
121
|
settings.encodeUrl = true;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
if (isNumber(timeout)) {
|
|
125
|
-
settings.timeout = timeout;
|
|
126
|
-
} else {
|
|
127
|
-
settings.timeout = 0;
|
|
128
|
-
}
|
|
124
|
+
settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
|
|
129
125
|
|
|
130
126
|
if (httpSettings?.followRedirects === true) {
|
|
131
127
|
settings.followRedirects = true;
|
|
@@ -142,6 +138,8 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
142
138
|
settings.maxRedirects = 5;
|
|
143
139
|
}
|
|
144
140
|
|
|
141
|
+
settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
|
|
142
|
+
|
|
145
143
|
ocRequest.settings = settings;
|
|
146
144
|
|
|
147
145
|
// examples
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { HttpRequestHeader, HttpResponseHeader, HttpRequestParam, HttpRequestBody } from '@opencollection/types/requests/http';
|
|
2
|
+
|
|
3
|
+
export interface MockRuleCondition {
|
|
4
|
+
target?: string;
|
|
5
|
+
key?: string;
|
|
6
|
+
operator?: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MockRouteRules {
|
|
11
|
+
operator?: 'AND' | 'OR';
|
|
12
|
+
conditions?: MockRuleCondition[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MockRouteEntry {
|
|
16
|
+
name?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
request?: {
|
|
19
|
+
method?: string;
|
|
20
|
+
url?: string;
|
|
21
|
+
headers?: HttpRequestHeader[];
|
|
22
|
+
params?: HttpRequestParam[];
|
|
23
|
+
body?: HttpRequestBody;
|
|
24
|
+
};
|
|
25
|
+
response?: {
|
|
26
|
+
status?: number;
|
|
27
|
+
statusText?: string;
|
|
28
|
+
headers?: HttpResponseHeader[];
|
|
29
|
+
body?: {
|
|
30
|
+
type?: string;
|
|
31
|
+
data?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
rules?: MockRouteRules;
|
|
35
|
+
copiedFrom?: {
|
|
36
|
+
example?: string | null;
|
|
37
|
+
requestPath?: string | null;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MockServerFile {
|
|
42
|
+
info?: {
|
|
43
|
+
name?: string;
|
|
44
|
+
type?: 'mock';
|
|
45
|
+
};
|
|
46
|
+
mock?: {
|
|
47
|
+
port?: number;
|
|
48
|
+
delay?: number;
|
|
49
|
+
source?: {
|
|
50
|
+
type?: 'collection' | 'spec';
|
|
51
|
+
path?: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
routes?: MockRouteEntry[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BrunoMockRoute {
|
|
58
|
+
name: string;
|
|
59
|
+
description: string;
|
|
60
|
+
request: {
|
|
61
|
+
url: string;
|
|
62
|
+
method: string;
|
|
63
|
+
headers: any[];
|
|
64
|
+
params: any[];
|
|
65
|
+
body: any;
|
|
66
|
+
};
|
|
67
|
+
response: {
|
|
68
|
+
status: number;
|
|
69
|
+
statusText: string;
|
|
70
|
+
headers: any[];
|
|
71
|
+
body: {
|
|
72
|
+
type: string;
|
|
73
|
+
content: string;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
rules: {
|
|
77
|
+
operator: 'AND' | 'OR';
|
|
78
|
+
conditions: Array<{ target: string; key: string; operator: string; value: string }>;
|
|
79
|
+
};
|
|
80
|
+
copiedFrom?: {
|
|
81
|
+
exampleName: string | null;
|
|
82
|
+
requestPathname: string | null;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface BrunoMockServer {
|
|
87
|
+
name: string;
|
|
88
|
+
port: number | null;
|
|
89
|
+
delay: number;
|
|
90
|
+
source: {
|
|
91
|
+
type: 'collection' | 'spec';
|
|
92
|
+
path: string;
|
|
93
|
+
} | null;
|
|
94
|
+
routes: BrunoMockRoute[];
|
|
95
|
+
}
|
|
@@ -52,6 +52,129 @@ request:
|
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
describe('parseCollection — script execution flow', () => {
|
|
56
|
+
it('reads flow: sequential from the bruno extension', () => {
|
|
57
|
+
const yml = `opencollection: "1.0.0"
|
|
58
|
+
info:
|
|
59
|
+
name: c
|
|
60
|
+
extensions:
|
|
61
|
+
bruno:
|
|
62
|
+
scripts:
|
|
63
|
+
flow: sequential
|
|
64
|
+
`;
|
|
65
|
+
const { brunoConfig } = parseCollection(yml);
|
|
66
|
+
expect(brunoConfig.scripts?.flow).toBe('sequential');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('reads flow: sandwich from the bruno extension', () => {
|
|
70
|
+
const yml = `opencollection: "1.0.0"
|
|
71
|
+
info:
|
|
72
|
+
name: c
|
|
73
|
+
extensions:
|
|
74
|
+
bruno:
|
|
75
|
+
scripts:
|
|
76
|
+
flow: sandwich
|
|
77
|
+
`;
|
|
78
|
+
const { brunoConfig } = parseCollection(yml);
|
|
79
|
+
expect(brunoConfig.scripts?.flow).toBe('sandwich');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('ignores an unrecognized flow value', () => {
|
|
83
|
+
const yml = `opencollection: "1.0.0"
|
|
84
|
+
info:
|
|
85
|
+
name: c
|
|
86
|
+
extensions:
|
|
87
|
+
bruno:
|
|
88
|
+
scripts:
|
|
89
|
+
flow: parallel
|
|
90
|
+
`;
|
|
91
|
+
const { brunoConfig } = parseCollection(yml);
|
|
92
|
+
expect(brunoConfig.scripts?.flow).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('has no scripts.flow when the file does not set one', () => {
|
|
96
|
+
const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n');
|
|
97
|
+
expect(brunoConfig.scripts?.flow).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('parseCollection — client certificates', () => {
|
|
102
|
+
it('maps pem and pkcs12 certs to Bruno cert/pfx shapes', () => {
|
|
103
|
+
const yml = `opencollection: "1.0.0"
|
|
104
|
+
info:
|
|
105
|
+
name: c
|
|
106
|
+
config:
|
|
107
|
+
clientCertificates:
|
|
108
|
+
- domain: localhost
|
|
109
|
+
type: pem
|
|
110
|
+
certificateFilePath: ./certs/client-cert.pem
|
|
111
|
+
privateKeyFilePath: ./certs/client-key.pem
|
|
112
|
+
passphrase: secret
|
|
113
|
+
- domain: example.com
|
|
114
|
+
type: pkcs12
|
|
115
|
+
pkcs12FilePath: ./certs/client.pfx
|
|
116
|
+
`;
|
|
117
|
+
const { brunoConfig } = parseCollection(yml);
|
|
118
|
+
|
|
119
|
+
expect(brunoConfig.clientCertificates.certs).toEqual([
|
|
120
|
+
{
|
|
121
|
+
domain: 'localhost',
|
|
122
|
+
type: 'cert',
|
|
123
|
+
certFilePath: './certs/client-cert.pem',
|
|
124
|
+
keyFilePath: './certs/client-key.pem',
|
|
125
|
+
passphrase: 'secret'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
domain: 'example.com',
|
|
129
|
+
type: 'pfx',
|
|
130
|
+
pfxFilePath: './certs/client.pfx',
|
|
131
|
+
passphrase: ''
|
|
132
|
+
}
|
|
133
|
+
]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('drops certs with an unrecognized type', () => {
|
|
137
|
+
const yml = `opencollection: "1.0.0"
|
|
138
|
+
info:
|
|
139
|
+
name: c
|
|
140
|
+
config:
|
|
141
|
+
clientCertificates:
|
|
142
|
+
- domain: localhost
|
|
143
|
+
type: bogus
|
|
144
|
+
certificateFilePath: ./certs/client-cert.pem
|
|
145
|
+
`;
|
|
146
|
+
const { brunoConfig } = parseCollection(yml);
|
|
147
|
+
|
|
148
|
+
expect(brunoConfig.clientCertificates.certs).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('reads a per-cert disabled flag when set to true, otherwise leaves it off', () => {
|
|
152
|
+
const yml = `opencollection: "1.0.0"
|
|
153
|
+
info:
|
|
154
|
+
name: c
|
|
155
|
+
config:
|
|
156
|
+
clientCertificates:
|
|
157
|
+
- domain: localhost
|
|
158
|
+
type: pem
|
|
159
|
+
certificateFilePath: ./certs/client-cert.pem
|
|
160
|
+
privateKeyFilePath: ./certs/client-key.pem
|
|
161
|
+
disabled: true
|
|
162
|
+
- domain: example.com
|
|
163
|
+
type: pkcs12
|
|
164
|
+
pkcs12FilePath: ./certs/client.pfx
|
|
165
|
+
disabled: true
|
|
166
|
+
- domain: other.com
|
|
167
|
+
type: pkcs12
|
|
168
|
+
pkcs12FilePath: ./certs/other.pfx
|
|
169
|
+
`;
|
|
170
|
+
const { brunoConfig } = parseCollection(yml);
|
|
171
|
+
|
|
172
|
+
expect(brunoConfig.clientCertificates.certs[0].disabled).toBe(true);
|
|
173
|
+
expect(brunoConfig.clientCertificates.certs[1].disabled).toBe(true);
|
|
174
|
+
expect(brunoConfig.clientCertificates.certs[2]).not.toHaveProperty('disabled');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
55
178
|
describe('parseCollection — reading the collection version', () => {
|
|
56
179
|
it('reads the version from the file as text', () => {
|
|
57
180
|
const { brunoConfig } = parseCollection('opencollection: "1.0.0"\ninfo:\n name: c\n version: v1.0.0\n');
|
|
@@ -38,11 +38,14 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
38
38
|
// presets
|
|
39
39
|
if (brunoExtension?.presets) {
|
|
40
40
|
const presets = brunoExtension.presets as BrunoPresetsExtension;
|
|
41
|
-
if (presets.request) {
|
|
41
|
+
if (presets.request || presets.defaultEnvironment) {
|
|
42
42
|
brunoConfig.presets = {
|
|
43
|
-
requestType: presets.request
|
|
44
|
-
requestUrl: presets.request
|
|
43
|
+
requestType: presets.request?.type || '',
|
|
44
|
+
requestUrl: presets.request?.url || ''
|
|
45
45
|
};
|
|
46
|
+
if (presets.defaultEnvironment) {
|
|
47
|
+
brunoConfig.presets.defaultEnvironment = presets.defaultEnvironment;
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -59,6 +62,14 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
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
|
+
}
|
|
62
73
|
if (Array.isArray(brunoExtensions?.openapi) && brunoExtensions.openapi.length > 0) {
|
|
63
74
|
brunoConfig.openapi = brunoExtensions.openapi.map((entry: any) => ({
|
|
64
75
|
sourceUrl: entry.sourceUrl,
|
|
@@ -142,14 +153,16 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
142
153
|
type: 'cert',
|
|
143
154
|
certFilePath: cert.certificateFilePath,
|
|
144
155
|
keyFilePath: cert.privateKeyFilePath,
|
|
145
|
-
passphrase: cert.passphrase || ''
|
|
156
|
+
passphrase: cert.passphrase || '',
|
|
157
|
+
...(cert.disabled === true && { disabled: true })
|
|
146
158
|
};
|
|
147
159
|
} else if (cert.type === 'pkcs12') {
|
|
148
160
|
return {
|
|
149
161
|
domain: cert.domain,
|
|
150
162
|
type: 'pfx',
|
|
151
163
|
pfxFilePath: cert.pkcs12FilePath,
|
|
152
|
-
passphrase: cert.passphrase || ''
|
|
164
|
+
passphrase: cert.passphrase || '',
|
|
165
|
+
...(cert.disabled === true && { disabled: true })
|
|
153
166
|
};
|
|
154
167
|
}
|
|
155
168
|
return null;
|
|
@@ -50,3 +50,21 @@ request:
|
|
|
50
50
|
expect(reqVars[5]).toMatchObject({ name: 'flag', value: 'maybe', dataType: 'boolean' });
|
|
51
51
|
});
|
|
52
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,11 +13,15 @@ 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
26
|
request: {
|
|
23
27
|
headers: [],
|
|
@@ -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
|
+
});
|