@usebruno/filestore 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/common/omit-headers.d.ts +3 -0
- package/dist/formats/yml/common/omit-headers.d.ts.map +1 -0
- package/dist/formats/yml/common/scripts.d.ts +3 -5
- package/dist/formats/yml/common/scripts.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/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/parseCollection.d.ts.map +1 -1
- package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
- package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/formats/bru/tests/redact-large-text-blocks.spec.js +60 -0
- package/src/formats/bru/utils/redact-large-text-blocks.ts +4 -0
- package/src/formats/yml/common/omit-headers.spec.ts +16 -0
- package/src/formats/yml/common/omit-headers.ts +12 -0
- package/src/formats/yml/common/scripts.spec.ts +155 -0
- package/src/formats/yml/common/scripts.ts +55 -29
- package/src/formats/yml/grpc-round-trip.spec.ts +110 -0
- package/src/formats/yml/items/parseGraphQLRequest.ts +2 -2
- package/src/formats/yml/items/parseGrpcRequest.ts +14 -6
- package/src/formats/yml/items/parseHttpRequest.ts +8 -2
- package/src/formats/yml/items/stringifyGraphQLRequest.ts +5 -9
- package/src/formats/yml/items/stringifyGrpcRequest.ts +2 -1
- package/src/formats/yml/items/stringifyHttpRequest.ts +11 -9
- package/src/formats/yml/items/stringifyWebsocketRequest.ts +3 -1
- package/src/formats/yml/parseCollection.ts +5 -9
- package/src/formats/yml/parseEnvironment.spec.ts +49 -0
- package/src/formats/yml/parseEnvironment.ts +6 -0
- package/src/formats/yml/script.spec.ts +159 -0
- package/src/formats/yml/stringifyCollection.ts +7 -10
- package/src/formats/yml/stringifyEnvironment.spec.ts +58 -0
- package/src/formats/yml/stringifyEnvironment.ts +6 -0
- package/src/formats/yml/stringifyFolder.ts +3 -1
- package/src/maxRedirects.spec.ts +144 -0
|
@@ -10,7 +10,7 @@ import { toBrunoScripts } from '../common/scripts';
|
|
|
10
10
|
import { toBrunoAssertions } from '../common/assertions';
|
|
11
11
|
import { uuid, ensureString } from '../../../utils';
|
|
12
12
|
import { utils } from '@usebruno/common';
|
|
13
|
-
const { toBool,
|
|
13
|
+
const { toBool, toMaxRedirects } = utils;
|
|
14
14
|
|
|
15
15
|
const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
|
|
16
16
|
const info = ocRequest.info;
|
|
@@ -123,7 +123,7 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
|
|
126
|
-
settings.maxRedirects =
|
|
126
|
+
settings.maxRedirects = toMaxRedirects(ocRequest.settings.maxRedirects);
|
|
127
127
|
settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
|
|
128
128
|
|
|
129
129
|
brunoItem.settings = settings;
|
|
@@ -51,8 +51,10 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
|
|
|
51
51
|
grpc: []
|
|
52
52
|
},
|
|
53
53
|
script: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
beforeCallStart: null,
|
|
55
|
+
beforeMessageSend: null,
|
|
56
|
+
afterMessageReceive: null,
|
|
57
|
+
afterCallEnd: null
|
|
56
58
|
},
|
|
57
59
|
vars: {
|
|
58
60
|
req: [],
|
|
@@ -80,11 +82,17 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
|
|
|
80
82
|
// scripts
|
|
81
83
|
const scripts = toBrunoScripts(runtime?.scripts);
|
|
82
84
|
if (scripts?.script && brunoRequest.script) {
|
|
83
|
-
if (scripts.script.
|
|
84
|
-
brunoRequest.script.
|
|
85
|
+
if (scripts.script.beforeCallStart) {
|
|
86
|
+
brunoRequest.script.beforeCallStart = scripts.script.beforeCallStart;
|
|
85
87
|
}
|
|
86
|
-
if (scripts.script.
|
|
87
|
-
brunoRequest.script.
|
|
88
|
+
if (scripts.script.beforeMessageSend) {
|
|
89
|
+
brunoRequest.script.beforeMessageSend = scripts.script.beforeMessageSend;
|
|
90
|
+
}
|
|
91
|
+
if (scripts.script.afterMessageReceive) {
|
|
92
|
+
brunoRequest.script.afterMessageReceive = scripts.script.afterMessageReceive;
|
|
93
|
+
}
|
|
94
|
+
if (scripts.script.afterCallEnd) {
|
|
95
|
+
brunoRequest.script.afterCallEnd = scripts.script.afterCallEnd;
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
if (scripts?.tests) {
|
|
@@ -10,9 +10,10 @@ import { toBrunoPostResponseVariables } from '../common/actions';
|
|
|
10
10
|
import { toBrunoScripts } from '../common/scripts';
|
|
11
11
|
import { toBrunoAssertions } from '../common/assertions';
|
|
12
12
|
import { toBrunoApp } from '../common/app';
|
|
13
|
+
import { normalizeOmitHeaders } from '../common/omit-headers';
|
|
13
14
|
import { uuid, ensureString } from '../../../utils';
|
|
14
15
|
import { utils } from '@usebruno/common';
|
|
15
|
-
const { toBool,
|
|
16
|
+
const { toBool, toMaxRedirects } = utils;
|
|
16
17
|
|
|
17
18
|
const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
18
19
|
const info = ocRequest.info;
|
|
@@ -126,9 +127,14 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
|
|
129
|
-
settings.maxRedirects =
|
|
130
|
+
settings.maxRedirects = toMaxRedirects(ocRequest.settings.maxRedirects);
|
|
130
131
|
settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
|
|
131
132
|
|
|
133
|
+
const omitHeaders = normalizeOmitHeaders(ocRequest.settings.omitHeaders);
|
|
134
|
+
if (omitHeaders) {
|
|
135
|
+
settings.omitHeaders = omitHeaders;
|
|
136
|
+
}
|
|
137
|
+
|
|
132
138
|
brunoItem.settings = settings;
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -8,7 +8,7 @@ import type { Assertion } from '@opencollection/types/common/assertions';
|
|
|
8
8
|
import type { Action } from '@opencollection/types/common/actions';
|
|
9
9
|
import type { HttpRequestParam, HttpRequestHeader } from '@opencollection/types/requests/http';
|
|
10
10
|
import { stringifyYml } from '../utils';
|
|
11
|
-
import { isNonEmptyString
|
|
11
|
+
import { isNonEmptyString } from '../../../utils';
|
|
12
12
|
import { toOpenCollectionAuth } from '../common/auth';
|
|
13
13
|
import { toOpenCollectionHttpHeaders } from '../common/headers';
|
|
14
14
|
import { toOpenCollectionParams } from '../common/params';
|
|
@@ -16,7 +16,8 @@ import { toOpenCollectionVariables } from '../common/variables';
|
|
|
16
16
|
import { toOpenCollectionActions } from '../common/actions';
|
|
17
17
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
18
18
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
19
|
-
import { resolveTimeoutSetting } from '@usebruno/common/utils';
|
|
19
|
+
import { resolveTimeoutSetting, toMaxRedirects } from '@usebruno/common/utils';
|
|
20
|
+
import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
|
|
20
21
|
|
|
21
22
|
const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
22
23
|
try {
|
|
@@ -97,7 +98,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// scripts
|
|
100
|
-
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
|
|
101
|
+
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
|
|
101
102
|
if (scripts) {
|
|
102
103
|
runtime.scripts = scripts;
|
|
103
104
|
hasRuntime = true;
|
|
@@ -143,12 +144,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
|
143
144
|
settings.followRedirects = true;
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
if (isNumber(maxRedirects)) {
|
|
148
|
-
settings.maxRedirects = maxRedirects;
|
|
149
|
-
} else {
|
|
150
|
-
settings.maxRedirects = 5;
|
|
151
|
-
}
|
|
147
|
+
settings.maxRedirects = toMaxRedirects(httpSettings?.maxRedirects);
|
|
152
148
|
|
|
153
149
|
settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
|
|
154
150
|
|
|
@@ -12,6 +12,7 @@ import { toOpenCollectionAuth } from '../common/auth';
|
|
|
12
12
|
import { toOpenCollectionVariables } from '../common/variables';
|
|
13
13
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
14
14
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
15
|
+
import { GRPC_SCRIPT_KEYS } from '@usebruno/common';
|
|
15
16
|
|
|
16
17
|
const stringifyGrpcRequest = (item: BrunoItem): string => {
|
|
17
18
|
try {
|
|
@@ -103,7 +104,7 @@ const stringifyGrpcRequest = (item: BrunoItem): string => {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
// scripts
|
|
106
|
-
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
|
|
107
|
+
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, GRPC_SCRIPT_KEYS);
|
|
107
108
|
if (scripts) {
|
|
108
109
|
runtime.scripts = scripts;
|
|
109
110
|
hasRuntime = true;
|
|
@@ -17,8 +17,10 @@ import { toOpenCollectionVariables } from '../common/variables';
|
|
|
17
17
|
import { toOpenCollectionActions } from '../common/actions';
|
|
18
18
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
19
19
|
import { toOpenCollectionAssertions } from '../common/assertions';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
import { normalizeOmitHeaders } from '../common/omit-headers';
|
|
21
|
+
import { isNonEmptyString } from '../../../utils';
|
|
22
|
+
import { resolveTimeoutSetting, toMaxRedirects } from '@usebruno/common/utils';
|
|
23
|
+
import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
|
|
22
24
|
|
|
23
25
|
const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
24
26
|
try {
|
|
@@ -85,7 +87,7 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
// scripts
|
|
88
|
-
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest);
|
|
90
|
+
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
|
|
89
91
|
if (scripts) {
|
|
90
92
|
runtime.scripts = scripts;
|
|
91
93
|
hasRuntime = true;
|
|
@@ -131,15 +133,15 @@ const stringifyHttpRequest = (item: BrunoItem): string => {
|
|
|
131
133
|
settings.followRedirects = true;
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
|
|
135
|
-
if (isNumber(maxRedirects)) {
|
|
136
|
-
settings.maxRedirects = maxRedirects;
|
|
137
|
-
} else {
|
|
138
|
-
settings.maxRedirects = 5;
|
|
139
|
-
}
|
|
136
|
+
settings.maxRedirects = toMaxRedirects(httpSettings?.maxRedirects);
|
|
140
137
|
|
|
141
138
|
settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
|
|
142
139
|
|
|
140
|
+
const omitHeaders = normalizeOmitHeaders(httpSettings?.omitHeaders);
|
|
141
|
+
if (omitHeaders) {
|
|
142
|
+
settings.omitHeaders = omitHeaders;
|
|
143
|
+
}
|
|
144
|
+
|
|
143
145
|
ocRequest.settings = settings;
|
|
144
146
|
|
|
145
147
|
// examples
|
|
@@ -11,6 +11,7 @@ import { toOpenCollectionAuth } from '../common/auth';
|
|
|
11
11
|
import { toOpenCollectionHttpHeaders } from '../common/headers';
|
|
12
12
|
import { toOpenCollectionVariables } from '../common/variables';
|
|
13
13
|
import { toOpenCollectionScripts } from '../common/scripts';
|
|
14
|
+
import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
|
|
14
15
|
|
|
15
16
|
const stringifyWebsocketRequest = (item: BrunoItem): string => {
|
|
16
17
|
try {
|
|
@@ -92,7 +93,8 @@ const stringifyWebsocketRequest = (item: BrunoItem): string => {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// scripts
|
|
95
|
-
|
|
96
|
+
// TODO: Modify to WS scripts once WS scripts are implemented.
|
|
97
|
+
const scripts: Scripts | undefined = toOpenCollectionScripts(brunoRequest, HTTP_SCRIPT_KEYS);
|
|
96
98
|
if (scripts) {
|
|
97
99
|
runtime.scripts = scripts;
|
|
98
100
|
hasRuntime = true;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OpenCollection } from '@opencollection/types';
|
|
2
2
|
import type { FolderRoot } from '@usebruno/schema-types/collection/folder';
|
|
3
|
+
import { normalizeOpenApiSyncConfigs } from '@usebruno/common';
|
|
3
4
|
import { parseYml } from './utils';
|
|
4
5
|
import { toBrunoAuth } from './common/auth';
|
|
5
6
|
import { toBrunoHttpHeaders } from './common/headers';
|
|
@@ -70,15 +71,10 @@ const parseCollection = (ymlString: string): ParsedCollection => {
|
|
|
70
71
|
flow: brunoExtensions.scripts.flow
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
...(entry.lastSyncDate && { lastSyncDate: entry.lastSyncDate }),
|
|
78
|
-
...(entry.specHash && { specHash: entry.specHash }),
|
|
79
|
-
autoCheck: entry.autoCheck !== false,
|
|
80
|
-
autoCheckInterval: entry.autoCheckInterval || 5
|
|
81
|
-
}));
|
|
74
|
+
|
|
75
|
+
const openApiEntries = normalizeOpenApiSyncConfigs(brunoExtensions?.openapi);
|
|
76
|
+
if (openApiEntries.length > 0) {
|
|
77
|
+
brunoConfig.openapi = openApiEntries;
|
|
82
78
|
}
|
|
83
79
|
|
|
84
80
|
// protobuf
|
|
@@ -96,4 +96,53 @@ variables:
|
|
|
96
96
|
expect(env.name).toBe('Untitled Environment');
|
|
97
97
|
expect(env.variables).toEqual([]);
|
|
98
98
|
});
|
|
99
|
+
|
|
100
|
+
describe('extends', () => {
|
|
101
|
+
it('parses the parent environment name', () => {
|
|
102
|
+
const env = parseEnvironment(`name: prod
|
|
103
|
+
extends: base
|
|
104
|
+
variables: []
|
|
105
|
+
`);
|
|
106
|
+
|
|
107
|
+
expect(env.extends).toBe('base');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('leaves extends absent for an environment that does not declare it', () => {
|
|
111
|
+
const env = parseEnvironment(`name: prod
|
|
112
|
+
variables: []
|
|
113
|
+
`);
|
|
114
|
+
|
|
115
|
+
expect(env).not.toHaveProperty('extends');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('ignores a parent reference that names nothing', () => {
|
|
119
|
+
const env = parseEnvironment(`name: prod
|
|
120
|
+
extends: " "
|
|
121
|
+
variables: []
|
|
122
|
+
`);
|
|
123
|
+
|
|
124
|
+
expect(env).not.toHaveProperty('extends');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('parses a list of parents unresolved so that a save does not delete it', () => {
|
|
128
|
+
const env = parseEnvironment(`name: prod
|
|
129
|
+
extends:
|
|
130
|
+
- base
|
|
131
|
+
- shared
|
|
132
|
+
variables: []
|
|
133
|
+
`);
|
|
134
|
+
|
|
135
|
+
expect(env.extends).toEqual(['base', 'shared']);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('ignores a parent reference that is neither a name nor a list', () => {
|
|
139
|
+
const env = parseEnvironment(`name: prod
|
|
140
|
+
extends:
|
|
141
|
+
parent: base
|
|
142
|
+
variables: []
|
|
143
|
+
`);
|
|
144
|
+
|
|
145
|
+
expect(env).not.toHaveProperty('extends');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
99
148
|
});
|
|
@@ -10,6 +10,7 @@ import { parseYml } from './utils';
|
|
|
10
10
|
import { uuid, ensureString } from '../../utils';
|
|
11
11
|
import { SECRET_PROVIDER_IDENTIFIER_FIELDS } from '../../constants';
|
|
12
12
|
import { isTypedValue, fromOpenCollectionTypedValue } from './common/datatype';
|
|
13
|
+
import { validatedEnvironmentExtendsFrom } from '@usebruno/common/utils';
|
|
13
14
|
|
|
14
15
|
const isSecretVariable = (v: Variable | SecretVariable): v is SecretVariable => {
|
|
15
16
|
return 'secret' in v && v.secret === true;
|
|
@@ -106,6 +107,11 @@ const parseEnvironment = (ymlString: string): BrunoEnvironment => {
|
|
|
106
107
|
color: ocEnvironment.color || null
|
|
107
108
|
};
|
|
108
109
|
|
|
110
|
+
const ocEnvironmentExtendsFrom = validatedEnvironmentExtendsFrom(ocEnvironment.extends);
|
|
111
|
+
if (ocEnvironmentExtendsFrom) {
|
|
112
|
+
brunoEnvironment.extends = ocEnvironmentExtendsFrom;
|
|
113
|
+
}
|
|
114
|
+
|
|
109
115
|
const externalSecrets = toBrunoExternalSecrets((ocEnvironment as any).externalSecrets);
|
|
110
116
|
if (externalSecrets) {
|
|
111
117
|
brunoEnvironment.externalSecrets = externalSecrets;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import stringifyItem from './stringifyItem';
|
|
2
|
+
import parseItem from './parseItem';
|
|
3
|
+
import stringifyCollection from './stringifyCollection';
|
|
4
|
+
import parseCollection from './parseCollection';
|
|
5
|
+
import stringifyFolder from './stringifyFolder';
|
|
6
|
+
import parseFolder from './parseFolder';
|
|
7
|
+
|
|
8
|
+
const GRPC_SLOTS = {
|
|
9
|
+
beforeCallStart: 'before()',
|
|
10
|
+
afterCallEnd: 'after()',
|
|
11
|
+
beforeMessageSend: 'beforeSend()',
|
|
12
|
+
afterMessageReceive: 'afterReceive()'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ALL_SLOTS = {
|
|
16
|
+
req: 'pre()',
|
|
17
|
+
res: 'post()',
|
|
18
|
+
...GRPC_SLOTS
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const HTTP_TYPES = ['before-request', 'after-response'];
|
|
22
|
+
const GRPC_TYPES = [
|
|
23
|
+
'grpc:before-call-start',
|
|
24
|
+
'grpc:after-call-end',
|
|
25
|
+
'grpc:before-message-send',
|
|
26
|
+
'grpc:after-message-receive'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const expectScriptTypes = (yml: string, written: string[], dropped: string[]) => {
|
|
30
|
+
written.forEach((type) => expect(yml).toContain(`type: ${type}`));
|
|
31
|
+
dropped.forEach((type) => expect(yml).not.toContain(`type: ${type}`));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const itemWithAllSlots = (type: string, request: Record<string, any>) => ({
|
|
35
|
+
uid: 'i1',
|
|
36
|
+
type,
|
|
37
|
+
name: 'r',
|
|
38
|
+
seq: 1,
|
|
39
|
+
request: {
|
|
40
|
+
auth: { mode: 'none' },
|
|
41
|
+
headers: [],
|
|
42
|
+
script: { ...ALL_SLOTS },
|
|
43
|
+
tests: null,
|
|
44
|
+
vars: { req: [], res: [] },
|
|
45
|
+
...request
|
|
46
|
+
}
|
|
47
|
+
}) as any;
|
|
48
|
+
|
|
49
|
+
const rootWithScript = (script: Record<string, string | null>) => ({
|
|
50
|
+
meta: { name: 'my-folder', seq: 1 },
|
|
51
|
+
request: {
|
|
52
|
+
headers: [],
|
|
53
|
+
auth: { mode: 'none' },
|
|
54
|
+
script,
|
|
55
|
+
tests: null,
|
|
56
|
+
vars: { req: [], res: [] }
|
|
57
|
+
},
|
|
58
|
+
docs: null
|
|
59
|
+
}) as any;
|
|
60
|
+
|
|
61
|
+
describe('script — request items', () => {
|
|
62
|
+
const httpFamily = [
|
|
63
|
+
[
|
|
64
|
+
'http',
|
|
65
|
+
itemWithAllSlots('http-request', {
|
|
66
|
+
url: 'https://example.com',
|
|
67
|
+
method: 'GET',
|
|
68
|
+
params: [],
|
|
69
|
+
body: { mode: 'none' }
|
|
70
|
+
})
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
'graphql',
|
|
74
|
+
itemWithAllSlots('graphql-request', {
|
|
75
|
+
url: 'https://example.com/graphql',
|
|
76
|
+
method: 'POST',
|
|
77
|
+
params: [],
|
|
78
|
+
body: { mode: 'graphql', graphql: { query: '{ hi }', variables: '' } }
|
|
79
|
+
})
|
|
80
|
+
],
|
|
81
|
+
// TODO: Move it to a separate test block, once ws scripts are added.
|
|
82
|
+
[
|
|
83
|
+
'websocket',
|
|
84
|
+
itemWithAllSlots('ws-request', {
|
|
85
|
+
url: 'wss://example.com',
|
|
86
|
+
body: { mode: 'ws', ws: [{ name: 'message 1', content: '{}' }] }
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
] as [string, any][];
|
|
90
|
+
|
|
91
|
+
it.each(httpFamily)('a %s request writes req/res and drops the grpc hooks', (_name, item) => {
|
|
92
|
+
const yml = stringifyItem(item);
|
|
93
|
+
|
|
94
|
+
expectScriptTypes(yml, HTTP_TYPES, GRPC_TYPES);
|
|
95
|
+
expect(yml).not.toContain('before()');
|
|
96
|
+
expect(yml).not.toContain('after()');
|
|
97
|
+
expect(yml).not.toContain('beforeSend()');
|
|
98
|
+
expect(yml).not.toContain('afterReceive()');
|
|
99
|
+
|
|
100
|
+
expect(parseItem(yml).request!.script).toEqual({ req: 'pre()', res: 'post()' });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('a grpc request writes the lifecycle hooks and drops req/res', () => {
|
|
104
|
+
const item = itemWithAllSlots('grpc-request', {
|
|
105
|
+
url: 'grpc://localhost:50051',
|
|
106
|
+
method: '/hello.Greeter/SayHello',
|
|
107
|
+
methodType: 'unary',
|
|
108
|
+
body: { mode: 'grpc', grpc: [{ name: 'message 1', content: '{}' }] },
|
|
109
|
+
assertions: []
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const yml = stringifyItem(item);
|
|
113
|
+
|
|
114
|
+
expectScriptTypes(yml, GRPC_TYPES, HTTP_TYPES);
|
|
115
|
+
expect(yml).not.toContain('pre()');
|
|
116
|
+
expect(yml).not.toContain('post()');
|
|
117
|
+
|
|
118
|
+
expect(parseItem(yml).request!.script).toEqual({
|
|
119
|
+
beforeCallStart: 'before()',
|
|
120
|
+
afterCallEnd: 'after()',
|
|
121
|
+
beforeMessageSend: 'beforeSend()',
|
|
122
|
+
afterMessageReceive: 'afterReceive()'
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('script — collection and folder roots', () => {
|
|
128
|
+
it('a collection root writes req/res and drops the grpc hooks', () => {
|
|
129
|
+
const yml = stringifyCollection(rootWithScript({ ...ALL_SLOTS }), { name: 'c' });
|
|
130
|
+
|
|
131
|
+
expectScriptTypes(yml, HTTP_TYPES, GRPC_TYPES);
|
|
132
|
+
|
|
133
|
+
expect(parseCollection(yml).collectionRoot.request!.script).toEqual({ req: 'pre()', res: 'post()' });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('a folder root writes req/res and drops the grpc hooks', () => {
|
|
137
|
+
const yml = stringifyFolder(rootWithScript({ ...ALL_SLOTS }));
|
|
138
|
+
|
|
139
|
+
expectScriptTypes(yml, HTTP_TYPES, GRPC_TYPES);
|
|
140
|
+
|
|
141
|
+
expect(parseFolder(yml).request!.script).toEqual({ req: 'pre()', res: 'post()' });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('a collection root carrying only grpc hooks writes no scripts at all', () => {
|
|
145
|
+
const yml = stringifyCollection(rootWithScript({ ...GRPC_SLOTS }), { name: 'c' });
|
|
146
|
+
|
|
147
|
+
expect(yml).not.toContain('scripts:');
|
|
148
|
+
|
|
149
|
+
expect(parseCollection(yml).collectionRoot.request?.script).toBeUndefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('a folder root carrying only grpc hooks writes no scripts at all', () => {
|
|
153
|
+
const yml = stringifyFolder(rootWithScript({ ...GRPC_SLOTS }));
|
|
154
|
+
|
|
155
|
+
expect(yml).not.toContain('scripts:');
|
|
156
|
+
|
|
157
|
+
expect(parseFolder(yml).request!.script).toEqual({ req: null, res: null });
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -5,6 +5,7 @@ import type { ClientCertificate, PemCertificate, Pkcs12Certificate } from '@open
|
|
|
5
5
|
import type { Variable } from '@opencollection/types/common/variables';
|
|
6
6
|
import type { Action } from '@opencollection/types/common/actions';
|
|
7
7
|
import type { Scripts } from '@opencollection/types/common/scripts';
|
|
8
|
+
import { normalizeOpenApiSyncConfigs } from '@usebruno/common';
|
|
8
9
|
import { stringifyYml } from './utils';
|
|
9
10
|
import { toOpenCollectionAuth } from './common/auth';
|
|
10
11
|
import { toOpenCollectionHttpHeaders } from './common/headers';
|
|
@@ -12,6 +13,7 @@ import { toOpenCollectionVariables } from './common/variables';
|
|
|
12
13
|
import { toOpenCollectionActions } from './common/actions';
|
|
13
14
|
import { toOpenCollectionScripts } from './common/scripts';
|
|
14
15
|
import type { Auth } from '@opencollection/types/common/auth';
|
|
16
|
+
import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
|
|
15
17
|
|
|
16
18
|
const hasCollectionConfig = (brunoConfig: any): boolean => {
|
|
17
19
|
// protobuf
|
|
@@ -209,7 +211,8 @@ const stringifyCollection = (collectionRoot: any, brunoConfig: any): string => {
|
|
|
209
211
|
|
|
210
212
|
// scripts
|
|
211
213
|
if (hasRequestScripts(collectionRoot)) {
|
|
212
|
-
|
|
214
|
+
// TODO: Widen scope to include GRPC scripts once Collection/Folder level inheritance is added to gRPC.
|
|
215
|
+
const ocScripts: Scripts | undefined = toOpenCollectionScripts(collectionRoot.request, HTTP_SCRIPT_KEYS);
|
|
213
216
|
if (ocScripts) {
|
|
214
217
|
oc.request.scripts = ocScripts;
|
|
215
218
|
}
|
|
@@ -280,18 +283,12 @@ const stringifyCollection = (collectionRoot: any, brunoConfig: any): string => {
|
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
// bruno-specific extensions
|
|
283
|
-
|
|
286
|
+
const openApiEntries = normalizeOpenApiSyncConfigs(brunoConfig.openapi);
|
|
287
|
+
if (openApiEntries.length > 0) {
|
|
284
288
|
if (!oc.extensions.bruno) {
|
|
285
289
|
oc.extensions.bruno = {};
|
|
286
290
|
}
|
|
287
|
-
(oc.extensions.bruno as any).openapi =
|
|
288
|
-
sourceUrl: entry.sourceUrl,
|
|
289
|
-
groupBy: entry.groupBy,
|
|
290
|
-
...(entry.lastSyncDate && { lastSyncDate: entry.lastSyncDate }),
|
|
291
|
-
...(entry.specHash && { specHash: entry.specHash }),
|
|
292
|
-
autoCheck: entry.autoCheck !== false,
|
|
293
|
-
autoCheckInterval: entry.autoCheckInterval || 5
|
|
294
|
-
}));
|
|
291
|
+
(oc.extensions.bruno as any).openapi = openApiEntries;
|
|
295
292
|
}
|
|
296
293
|
|
|
297
294
|
return stringifyYml(oc);
|
|
@@ -88,4 +88,62 @@ describe('stringifyEnvironment', () => {
|
|
|
88
88
|
const reparsed = parseEnvironment(stringifyEnvironment(env));
|
|
89
89
|
expect(reparsed.color).toBe('#ff0000');
|
|
90
90
|
});
|
|
91
|
+
|
|
92
|
+
describe('extends', () => {
|
|
93
|
+
it('round-trips the parent environment name', () => {
|
|
94
|
+
const env = {
|
|
95
|
+
uid: 'env-uid',
|
|
96
|
+
name: 'prod',
|
|
97
|
+
extends: 'base',
|
|
98
|
+
variables: [{ uid: 'u1', name: 'host', value: 'prod.example.com', type: 'text', enabled: true, secret: false }]
|
|
99
|
+
} as any;
|
|
100
|
+
|
|
101
|
+
const reparsed = parseEnvironment(stringifyEnvironment(env));
|
|
102
|
+
|
|
103
|
+
expect(reparsed.extends).toBe('base');
|
|
104
|
+
expect(reparsed.variables[0]).toMatchObject({ name: 'host', value: 'prod.example.com' });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('omits extends when no parent is set', () => {
|
|
108
|
+
const env = { uid: 'env-uid', name: 'prod', variables: [] } as any;
|
|
109
|
+
|
|
110
|
+
expect(stringifyEnvironment(env)).not.toContain('extends');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('omits extends when the parent names nothing', () => {
|
|
114
|
+
const env = { uid: 'env-uid', name: 'prod', extends: ' ', variables: [] } as any;
|
|
115
|
+
|
|
116
|
+
expect(stringifyEnvironment(env)).not.toContain('extends');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('round-trips a list of parents', () => {
|
|
120
|
+
const env = { uid: 'env-uid', name: 'prod', extends: ['base', 'shared'], variables: [] } as any;
|
|
121
|
+
|
|
122
|
+
const reparsed = parseEnvironment(stringifyEnvironment(env));
|
|
123
|
+
|
|
124
|
+
expect(reparsed.extends).toEqual(['base', 'shared']);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('omits extends when the list of parents is empty', () => {
|
|
128
|
+
const env = { uid: 'env-uid', name: 'prod', extends: [], variables: [] } as any;
|
|
129
|
+
|
|
130
|
+
expect(stringifyEnvironment(env)).not.toContain('extends');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('never writes the inheritedFrom provenance of a resolved variable', () => {
|
|
134
|
+
const env = {
|
|
135
|
+
uid: 'env-uid',
|
|
136
|
+
name: 'prod',
|
|
137
|
+
extends: 'base',
|
|
138
|
+
variables: [
|
|
139
|
+
{ uid: 'u1', name: 'host', value: 'base.example.com', type: 'text', enabled: true, secret: false, inheritedFrom: 'base' }
|
|
140
|
+
]
|
|
141
|
+
} as any;
|
|
142
|
+
|
|
143
|
+
const yml = stringifyEnvironment(env);
|
|
144
|
+
|
|
145
|
+
expect(yml).not.toContain('inheritedFrom');
|
|
146
|
+
expect(parseEnvironment(yml).variables[0]).not.toHaveProperty('inheritedFrom');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
91
149
|
});
|
|
@@ -10,6 +10,7 @@ import { stringifyYml } from './utils';
|
|
|
10
10
|
import { ensureString } from '../../utils';
|
|
11
11
|
import { SECRET_PROVIDER_IDENTIFIER_FIELDS } from '../../constants';
|
|
12
12
|
import { hasTypedMetadata, toOpenCollectionTypedValue, serializeVariableValue } from './common/datatype';
|
|
13
|
+
import { validatedEnvironmentExtendsFrom } from '@usebruno/common/utils';
|
|
13
14
|
|
|
14
15
|
export const toOpenCollectionEnvironmentVariables = (variables: (BrunoEnvironmentVariable | BrunoSecretEnvironmentVariable)[]): (Variable | SecretVariable)[] | undefined => {
|
|
15
16
|
if (!variables?.length) {
|
|
@@ -90,6 +91,11 @@ const stringifyEnvironment = (environment: BrunoEnvironment): string => {
|
|
|
90
91
|
name: environment.name
|
|
91
92
|
};
|
|
92
93
|
|
|
94
|
+
const environmentExtendsFrom = validatedEnvironmentExtendsFrom(environment.extends);
|
|
95
|
+
if (environmentExtendsFrom) {
|
|
96
|
+
(ocEnvironment as any).extends = environmentExtendsFrom;
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
if (environment.color) {
|
|
94
100
|
ocEnvironment.color = environment.color;
|
|
95
101
|
}
|
|
@@ -12,6 +12,7 @@ import { toOpenCollectionVariables } from './common/variables';
|
|
|
12
12
|
import { toOpenCollectionActions } from './common/actions';
|
|
13
13
|
import { toOpenCollectionScripts } from './common/scripts';
|
|
14
14
|
import { stringifyYml } from './utils';
|
|
15
|
+
import { HTTP_SCRIPT_KEYS } from '@usebruno/common';
|
|
15
16
|
|
|
16
17
|
const hasRequestDefaults = (folderRoot: FolderRoot): boolean => {
|
|
17
18
|
const requestDefaults = folderRoot?.request;
|
|
@@ -88,7 +89,8 @@ const stringifyFolder = (folderRoot: FolderRoot): string => {
|
|
|
88
89
|
|
|
89
90
|
// scripts
|
|
90
91
|
if (hasRequestScripts(folderRoot)) {
|
|
91
|
-
|
|
92
|
+
// TODO: Widen scope to include GRPC scripts once Collection/Folder level inheritance is added to GRPC.
|
|
93
|
+
const ocScripts: Scripts | undefined = toOpenCollectionScripts(folderRoot?.request, HTTP_SCRIPT_KEYS);
|
|
92
94
|
if (ocScripts) {
|
|
93
95
|
ocFolder.request.scripts = ocScripts;
|
|
94
96
|
}
|