@redocly/openapi-core 1.23.1 → 1.25.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/CHANGELOG.md +18 -0
- package/lib/config/all.js +3 -0
- package/lib/config/config-resolvers.d.ts +1 -1
- package/lib/config/config-resolvers.js +42 -26
- package/lib/config/config.js +4 -1
- package/lib/config/minimal.js +3 -0
- package/lib/config/recommended-strict.js +3 -0
- package/lib/config/recommended.js +3 -0
- package/lib/config/types.d.ts +10 -0
- package/lib/rules/arazzo/criteria-unique.d.ts +2 -0
- package/lib/rules/arazzo/criteria-unique.js +65 -0
- package/lib/rules/arazzo/index.js +6 -0
- package/lib/rules/spot/no-actions-type-end.d.ts +2 -0
- package/lib/rules/spot/no-actions-type-end.js +28 -0
- package/lib/rules/spot/no-criteria-xpath.d.ts +2 -0
- package/lib/rules/spot/no-criteria-xpath.js +21 -0
- package/lib/types/arazzo.js +1 -1
- package/lib/types/redocly-yaml.d.ts +1 -1
- package/lib/types/redocly-yaml.js +3 -0
- package/package.json +2 -2
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +6 -0
- package/src/config/__tests__/config-resolvers.test.ts +54 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-plugin-init.yaml +2 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-realm-plugin.yaml +2 -0
- package/src/config/__tests__/fixtures/resolve-config/plugin-with-init-logic.js +9 -0
- package/src/config/__tests__/fixtures/resolve-config/realm-plugin.js +12 -0
- package/src/config/all.ts +3 -0
- package/src/config/config-resolvers.ts +52 -29
- package/src/config/config.ts +4 -1
- package/src/config/minimal.ts +3 -0
- package/src/config/recommended-strict.ts +3 -0
- package/src/config/recommended.ts +3 -0
- package/src/config/types.ts +16 -0
- package/src/rules/arazzo/__tests__/criteria-unique.test.ts +161 -0
- package/src/rules/arazzo/__tests__/no-actions-type-end.test.ts +122 -0
- package/src/rules/arazzo/__tests__/no-criteria-xpath.test.ts +127 -0
- package/src/rules/arazzo/criteria-unique.ts +63 -0
- package/src/rules/arazzo/index.ts +6 -0
- package/src/rules/spot/no-actions-type-end.ts +27 -0
- package/src/rules/spot/no-criteria-xpath.ts +20 -0
- package/src/types/arazzo.ts +1 -1
- package/src/types/redocly-yaml.ts +3 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -43,6 +43,9 @@ const DEFAULT_PROJECT_PLUGIN_PATHS = ['@theme/plugin.js', '@theme/plugin.cjs', '
|
|
|
43
43
|
// Workaround for dynamic imports being transpiled to require by Typescript: https://github.com/microsoft/TypeScript/issues/43329#issuecomment-811606238
|
|
44
44
|
const _importDynamic = new Function('modulePath', 'return import(modulePath)');
|
|
45
45
|
|
|
46
|
+
// Cache instantiated plugins during a single execution
|
|
47
|
+
const pluginsCache: Map<string, Plugin> = new Map();
|
|
48
|
+
|
|
46
49
|
export async function resolveConfigFileAndRefs({
|
|
47
50
|
configPath,
|
|
48
51
|
externalRefResolver = new BaseResolver(),
|
|
@@ -111,9 +114,9 @@ export async function resolveConfig({
|
|
|
111
114
|
);
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
function getDefaultPluginPath(
|
|
117
|
+
function getDefaultPluginPath(configDir: string): string | undefined {
|
|
115
118
|
for (const pluginPath of DEFAULT_PROJECT_PLUGIN_PATHS) {
|
|
116
|
-
const absolutePluginPath = path.resolve(
|
|
119
|
+
const absolutePluginPath = path.resolve(configDir, pluginPath);
|
|
117
120
|
if (existsSync(absolutePluginPath)) {
|
|
118
121
|
return pluginPath;
|
|
119
122
|
}
|
|
@@ -123,32 +126,58 @@ function getDefaultPluginPath(configPath: string): string | undefined {
|
|
|
123
126
|
|
|
124
127
|
export async function resolvePlugins(
|
|
125
128
|
plugins: (string | Plugin)[] | null,
|
|
126
|
-
|
|
129
|
+
configDir: string = ''
|
|
127
130
|
): Promise<Plugin[]> {
|
|
128
131
|
if (!plugins) return [];
|
|
129
132
|
|
|
130
133
|
// TODO: implement or reuse Resolver approach so it will work in node and browser envs
|
|
131
|
-
const requireFunc = async (plugin: string | Plugin): Promise<
|
|
134
|
+
const requireFunc = async (plugin: string | Plugin): Promise<Plugin | undefined> => {
|
|
132
135
|
if (isString(plugin)) {
|
|
133
136
|
try {
|
|
134
|
-
const maybeAbsolutePluginPath = path.resolve(
|
|
137
|
+
const maybeAbsolutePluginPath = path.resolve(configDir, plugin);
|
|
135
138
|
|
|
136
139
|
const absolutePluginPath = existsSync(maybeAbsolutePluginPath)
|
|
137
140
|
? maybeAbsolutePluginPath
|
|
138
141
|
: // For plugins imported from packages specifically
|
|
139
142
|
require.resolve(plugin);
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
if (!pluginsCache.has(absolutePluginPath)) {
|
|
145
|
+
let requiredPlugin: ImportedPlugin | undefined;
|
|
146
|
+
|
|
144
147
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
145
148
|
// @ts-ignore
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (typeof __webpack_require__ === 'function') {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
requiredPlugin = __non_webpack_require__(absolutePluginPath);
|
|
153
|
+
} else {
|
|
154
|
+
// you can import both cjs and mjs
|
|
155
|
+
const mod = await _importDynamic(pathToFileURL(absolutePluginPath).href);
|
|
156
|
+
requiredPlugin = mod.default || mod;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const pluginCreatorOptions = { contentDir: configDir };
|
|
160
|
+
|
|
161
|
+
const pluginModule = isDeprecatedPluginFormat(requiredPlugin)
|
|
162
|
+
? requiredPlugin
|
|
163
|
+
: isCommonJsPlugin(requiredPlugin)
|
|
164
|
+
? await requiredPlugin(pluginCreatorOptions)
|
|
165
|
+
: await requiredPlugin?.default?.(pluginCreatorOptions);
|
|
166
|
+
|
|
167
|
+
if (pluginModule?.id && isDeprecatedPluginFormat(requiredPlugin)) {
|
|
168
|
+
logger.info(`Deprecated plugin format detected: ${pluginModule.id}\n`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (pluginModule) {
|
|
172
|
+
pluginsCache.set(absolutePluginPath, {
|
|
173
|
+
...pluginModule,
|
|
174
|
+
path: plugin,
|
|
175
|
+
absolutePath: absolutePluginPath,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
151
178
|
}
|
|
179
|
+
|
|
180
|
+
return pluginsCache.get(absolutePluginPath);
|
|
152
181
|
} catch (e) {
|
|
153
182
|
throw new Error(`Failed to load plugin "${plugin}": ${e.message}\n\n${e.stack}`);
|
|
154
183
|
}
|
|
@@ -162,7 +191,7 @@ export async function resolvePlugins(
|
|
|
162
191
|
/**
|
|
163
192
|
* Include the default plugin automatically if it's not in configuration
|
|
164
193
|
*/
|
|
165
|
-
const defaultPluginPath = getDefaultPluginPath(
|
|
194
|
+
const defaultPluginPath = getDefaultPluginPath(configDir);
|
|
166
195
|
if (defaultPluginPath) {
|
|
167
196
|
plugins.push(defaultPluginPath);
|
|
168
197
|
}
|
|
@@ -182,24 +211,12 @@ export async function resolvePlugins(
|
|
|
182
211
|
resolvedPlugins.add(p);
|
|
183
212
|
}
|
|
184
213
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const pluginCreatorOptions = { contentDir: path.dirname(configPath) };
|
|
188
|
-
|
|
189
|
-
const pluginModule = isDeprecatedPluginFormat(requiredPlugin)
|
|
190
|
-
? requiredPlugin
|
|
191
|
-
: isCommonJsPlugin(requiredPlugin)
|
|
192
|
-
? await requiredPlugin(pluginCreatorOptions)
|
|
193
|
-
: await requiredPlugin?.default?.(pluginCreatorOptions);
|
|
214
|
+
const pluginModule: Plugin | undefined = await requireFunc(p);
|
|
194
215
|
|
|
195
216
|
if (!pluginModule) {
|
|
196
217
|
return;
|
|
197
218
|
}
|
|
198
219
|
|
|
199
|
-
if (isString(p) && pluginModule.id && isDeprecatedPluginFormat(requiredPlugin)) {
|
|
200
|
-
logger.info(`Deprecated plugin format detected: ${pluginModule.id}\n`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
220
|
const id = pluginModule.id;
|
|
204
221
|
if (typeof id !== 'string') {
|
|
205
222
|
throw new Error(
|
|
@@ -313,7 +330,10 @@ export async function resolvePlugins(
|
|
|
313
330
|
plugin.assertions = pluginModule.assertions;
|
|
314
331
|
}
|
|
315
332
|
|
|
316
|
-
return
|
|
333
|
+
return {
|
|
334
|
+
...pluginModule,
|
|
335
|
+
...plugin,
|
|
336
|
+
};
|
|
317
337
|
})
|
|
318
338
|
);
|
|
319
339
|
|
|
@@ -371,7 +391,10 @@ async function resolveAndMergeNestedStyleguideConfig(
|
|
|
371
391
|
? // In browser, we don't support plugins from config file yet
|
|
372
392
|
[defaultPlugin]
|
|
373
393
|
: getUniquePlugins(
|
|
374
|
-
await resolvePlugins(
|
|
394
|
+
await resolvePlugins(
|
|
395
|
+
[...(styleguideConfig?.plugins || []), defaultPlugin],
|
|
396
|
+
path.dirname(configPath)
|
|
397
|
+
)
|
|
375
398
|
);
|
|
376
399
|
const pluginPaths = styleguideConfig?.plugins
|
|
377
400
|
?.filter(isString)
|
package/src/config/config.ts
CHANGED
|
@@ -73,7 +73,10 @@ export class StyleguideConfig {
|
|
|
73
73
|
[SpecVersion.OAS3_1]: { ...rawConfig.rules, ...rawConfig.oas3_1Rules },
|
|
74
74
|
[SpecVersion.Async2]: { ...rawConfig.rules, ...rawConfig.async2Rules },
|
|
75
75
|
[SpecVersion.Async3]: { ...rawConfig.rules, ...rawConfig.async3Rules },
|
|
76
|
-
[SpecVersion.Arazzo]: {
|
|
76
|
+
[SpecVersion.Arazzo]: {
|
|
77
|
+
...rawConfig.arazzoRules,
|
|
78
|
+
...(rawConfig.rules?.assertions ? { assertions: rawConfig.rules.assertions } : {}),
|
|
79
|
+
},
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
this.preprocessors = {
|
package/src/config/minimal.ts
CHANGED
|
@@ -121,6 +121,9 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
|
|
|
121
121
|
'step-onSuccess-unique': 'off',
|
|
122
122
|
'step-onFailure-unique': 'off',
|
|
123
123
|
'requestBody-replacements-unique': 'off',
|
|
124
|
+
'no-criteria-xpath': 'off',
|
|
125
|
+
'no-actions-type-end': 'off',
|
|
126
|
+
'criteria-unique': 'off',
|
|
124
127
|
},
|
|
125
128
|
};
|
|
126
129
|
|
|
@@ -121,6 +121,9 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
|
|
|
121
121
|
'step-onSuccess-unique': 'error',
|
|
122
122
|
'step-onFailure-unique': 'error',
|
|
123
123
|
'requestBody-replacements-unique': 'error',
|
|
124
|
+
'no-criteria-xpath': 'error',
|
|
125
|
+
'no-actions-type-end': 'error',
|
|
126
|
+
'criteria-unique': 'error',
|
|
124
127
|
},
|
|
125
128
|
};
|
|
126
129
|
|
|
@@ -121,6 +121,9 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
|
|
|
121
121
|
'step-onSuccess-unique': 'warn',
|
|
122
122
|
'step-onFailure-unique': 'warn',
|
|
123
123
|
'requestBody-replacements-unique': 'warn',
|
|
124
|
+
'no-criteria-xpath': 'warn',
|
|
125
|
+
'no-actions-type-end': 'warn',
|
|
126
|
+
'criteria-unique': 'warn',
|
|
124
127
|
},
|
|
125
128
|
};
|
|
126
129
|
|
package/src/config/types.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
BuiltInOAS3RuleId,
|
|
31
31
|
BuiltInArazzoRuleId,
|
|
32
32
|
} from '../types/redocly-yaml';
|
|
33
|
+
import type { JSONSchema } from 'json-schema-to-ts';
|
|
33
34
|
|
|
34
35
|
export type RuleSeverity = ProblemSeverity | 'off';
|
|
35
36
|
|
|
@@ -136,12 +137,27 @@ export type AssertionsConfig = Record<string, CustomFunction>;
|
|
|
136
137
|
|
|
137
138
|
export type Plugin = {
|
|
138
139
|
id: string;
|
|
140
|
+
|
|
139
141
|
configs?: Record<string, PluginStyleguideConfig>;
|
|
140
142
|
rules?: CustomRulesConfig;
|
|
141
143
|
preprocessors?: PreprocessorsConfig;
|
|
142
144
|
decorators?: DecoratorsConfig;
|
|
143
145
|
typeExtension?: TypeExtensionsConfig;
|
|
144
146
|
assertions?: AssertionsConfig;
|
|
147
|
+
|
|
148
|
+
// Realm properties
|
|
149
|
+
path?: string;
|
|
150
|
+
absolutePath?: string;
|
|
151
|
+
processContent?: (actions: any, content: any) => Promise<void> | void;
|
|
152
|
+
afterRoutesCreated?: (actions: any, content: any) => Promise<void> | void;
|
|
153
|
+
loaders?: Record<
|
|
154
|
+
string,
|
|
155
|
+
(path: string, context: any, reportError: (error: Error) => void) => Promise<unknown>
|
|
156
|
+
>;
|
|
157
|
+
requiredEntitlements?: string[];
|
|
158
|
+
ssoConfigSchema?: JSONSchema;
|
|
159
|
+
redoclyConfigSchema?: JSONSchema;
|
|
160
|
+
ejectIgnore?: string[];
|
|
145
161
|
};
|
|
146
162
|
|
|
147
163
|
type PluginCreatorOptions = {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
import { lintDocument } from '../../../lint';
|
|
3
|
+
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('Arazzo criteria-unique', () => {
|
|
7
|
+
const document = parseYamlToDocument(
|
|
8
|
+
outdent`
|
|
9
|
+
arazzo: '1.0.0'
|
|
10
|
+
info:
|
|
11
|
+
title: Cool API
|
|
12
|
+
version: 1.0.0
|
|
13
|
+
description: A cool API
|
|
14
|
+
sourceDescriptions:
|
|
15
|
+
- name: museum-api
|
|
16
|
+
type: openapi
|
|
17
|
+
url: openapi.yaml
|
|
18
|
+
workflows:
|
|
19
|
+
- workflowId: get-museum-hours
|
|
20
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
21
|
+
parameters:
|
|
22
|
+
- in: header
|
|
23
|
+
name: Authorization
|
|
24
|
+
value: Basic Og==
|
|
25
|
+
steps:
|
|
26
|
+
- stepId: create-event
|
|
27
|
+
description: >-
|
|
28
|
+
Create a new special event.
|
|
29
|
+
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
|
30
|
+
requestBody:
|
|
31
|
+
payload:
|
|
32
|
+
name: 'Mermaid Treasure Identification and Analysis'
|
|
33
|
+
location: 'Under the seaaa 🦀 🎶 🌊.'
|
|
34
|
+
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
|
35
|
+
dates:
|
|
36
|
+
- '2023-09-05'
|
|
37
|
+
- '2023-09-08'
|
|
38
|
+
price: 0
|
|
39
|
+
successCriteria:
|
|
40
|
+
- condition: $statusCode == 200
|
|
41
|
+
- condition: $statusCode == 200
|
|
42
|
+
- context: $response.body
|
|
43
|
+
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
|
44
|
+
type: jsonpath
|
|
45
|
+
- context: $response.body
|
|
46
|
+
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
|
47
|
+
type: jsonpath
|
|
48
|
+
onSuccess:
|
|
49
|
+
- name: 'onSuccessActionName'
|
|
50
|
+
type: 'goto'
|
|
51
|
+
stepId: 'buy-ticket'
|
|
52
|
+
criteria:
|
|
53
|
+
- condition: $response.body.open == true
|
|
54
|
+
- condition: $response.body.open == true
|
|
55
|
+
onFailure:
|
|
56
|
+
- name: 'onFailureActionName'
|
|
57
|
+
type: 'goto'
|
|
58
|
+
stepId: 'buy-ticket'
|
|
59
|
+
criteria:
|
|
60
|
+
- condition: $response.body.open == true
|
|
61
|
+
- condition: $response.body.open == true
|
|
62
|
+
outputs:
|
|
63
|
+
createdEventId: $response.body.eventId
|
|
64
|
+
name: $response.body.name
|
|
65
|
+
- workflowId: get-museum-hours-2
|
|
66
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
67
|
+
parameters:
|
|
68
|
+
- in: header
|
|
69
|
+
name: Authorization
|
|
70
|
+
value: Basic Og==
|
|
71
|
+
steps:
|
|
72
|
+
- stepId: get-museum-hours
|
|
73
|
+
description: >-
|
|
74
|
+
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
|
75
|
+
operationId: museum-api.getMuseumHours
|
|
76
|
+
successCriteria:
|
|
77
|
+
- condition: $statusCode == 200
|
|
78
|
+
`,
|
|
79
|
+
'arazzo.yaml'
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
it('should report when the duplicated criteria exists', async () => {
|
|
83
|
+
const results = await lintDocument({
|
|
84
|
+
externalRefResolver: new BaseResolver(),
|
|
85
|
+
document,
|
|
86
|
+
config: await makeConfig({
|
|
87
|
+
rules: {},
|
|
88
|
+
arazzoRules: { 'criteria-unique': 'error' },
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"location": [
|
|
96
|
+
{
|
|
97
|
+
"pointer": "#/workflows/0/steps/0/successCriteria/1",
|
|
98
|
+
"reportOnKey": false,
|
|
99
|
+
"source": "arazzo.yaml",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
"message": "The Step SuccessCriteria items must be unique.",
|
|
103
|
+
"ruleId": "criteria-unique",
|
|
104
|
+
"severity": "error",
|
|
105
|
+
"suggest": [],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"location": [
|
|
109
|
+
{
|
|
110
|
+
"pointer": "#/workflows/0/steps/0/successCriteria/3",
|
|
111
|
+
"reportOnKey": false,
|
|
112
|
+
"source": "arazzo.yaml",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
"message": "The Step SuccessCriteria items must be unique.",
|
|
116
|
+
"ruleId": "criteria-unique",
|
|
117
|
+
"severity": "error",
|
|
118
|
+
"suggest": [],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"location": [
|
|
122
|
+
{
|
|
123
|
+
"pointer": "#/workflows/0/steps/0/onSuccess/0/criteria/1",
|
|
124
|
+
"reportOnKey": false,
|
|
125
|
+
"source": "arazzo.yaml",
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
"message": "The SuccessAction criteria items must be unique.",
|
|
129
|
+
"ruleId": "criteria-unique",
|
|
130
|
+
"severity": "error",
|
|
131
|
+
"suggest": [],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"location": [
|
|
135
|
+
{
|
|
136
|
+
"pointer": "#/workflows/0/steps/0/onFailure/0/criteria/1",
|
|
137
|
+
"reportOnKey": false,
|
|
138
|
+
"source": "arazzo.yaml",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
"message": "The FailureAction criteria items must be unique.",
|
|
142
|
+
"ruleId": "criteria-unique",
|
|
143
|
+
"severity": "error",
|
|
144
|
+
"suggest": [],
|
|
145
|
+
},
|
|
146
|
+
]
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should not report when the duplicated criteria exists', async () => {
|
|
151
|
+
const results = await lintDocument({
|
|
152
|
+
externalRefResolver: new BaseResolver(),
|
|
153
|
+
document,
|
|
154
|
+
config: await makeConfig({
|
|
155
|
+
rules: {},
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
import { lintDocument } from '../../../lint';
|
|
3
|
+
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('Arazzo no-actions-type-end', () => {
|
|
7
|
+
const document = parseYamlToDocument(
|
|
8
|
+
outdent`
|
|
9
|
+
arazzo: '1.0.0'
|
|
10
|
+
info:
|
|
11
|
+
title: Cool API
|
|
12
|
+
version: 1.0.0
|
|
13
|
+
description: A cool API
|
|
14
|
+
sourceDescriptions:
|
|
15
|
+
- name: museum-api
|
|
16
|
+
type: openapi
|
|
17
|
+
url: openapi.yaml
|
|
18
|
+
workflows:
|
|
19
|
+
- workflowId: get-museum-hours
|
|
20
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
21
|
+
parameters:
|
|
22
|
+
- in: header
|
|
23
|
+
name: Authorization
|
|
24
|
+
value: Basic Og==
|
|
25
|
+
steps:
|
|
26
|
+
- stepId: create-event
|
|
27
|
+
description: >-
|
|
28
|
+
Create a new special event.
|
|
29
|
+
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
|
30
|
+
requestBody:
|
|
31
|
+
payload:
|
|
32
|
+
name: 'Mermaid Treasure Identification and Analysis'
|
|
33
|
+
location: 'Under the seaaa 🦀 🎶 🌊.'
|
|
34
|
+
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
|
35
|
+
dates:
|
|
36
|
+
- '2023-09-05'
|
|
37
|
+
- '2023-09-08'
|
|
38
|
+
price: 0
|
|
39
|
+
successCriteria:
|
|
40
|
+
- condition: $statusCode == 201
|
|
41
|
+
onSuccess:
|
|
42
|
+
- name: 'onSuccessActionName'
|
|
43
|
+
type: 'end'
|
|
44
|
+
stepId: 'buy-ticket'
|
|
45
|
+
onFailure:
|
|
46
|
+
- name: 'onFailureActionName'
|
|
47
|
+
type: 'end'
|
|
48
|
+
stepId: 'buy-ticket'
|
|
49
|
+
outputs:
|
|
50
|
+
createdEventId: $response.body.eventId
|
|
51
|
+
name: $response.body.name
|
|
52
|
+
- workflowId: get-museum-hours-2
|
|
53
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
54
|
+
parameters:
|
|
55
|
+
- in: header
|
|
56
|
+
name: Authorization
|
|
57
|
+
value: Basic Og==
|
|
58
|
+
steps:
|
|
59
|
+
- stepId: get-museum-hours
|
|
60
|
+
description: >-
|
|
61
|
+
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
|
62
|
+
operationId: museum-api.getMuseumHours
|
|
63
|
+
successCriteria:
|
|
64
|
+
- condition: $statusCode == 200
|
|
65
|
+
`,
|
|
66
|
+
'arazzo.yaml'
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
it('should report when the type `end` action exists', async () => {
|
|
70
|
+
const results = await lintDocument({
|
|
71
|
+
externalRefResolver: new BaseResolver(),
|
|
72
|
+
document,
|
|
73
|
+
config: await makeConfig({
|
|
74
|
+
rules: {},
|
|
75
|
+
arazzoRules: { 'no-actions-type-end': 'error' },
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
80
|
+
[
|
|
81
|
+
{
|
|
82
|
+
"location": [
|
|
83
|
+
{
|
|
84
|
+
"pointer": "#/workflows/0/steps/0/onSuccess/0/type",
|
|
85
|
+
"reportOnKey": false,
|
|
86
|
+
"source": "arazzo.yaml",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
"message": "The \`end\` type action is not supported by Spot.",
|
|
90
|
+
"ruleId": "no-actions-type-end",
|
|
91
|
+
"severity": "error",
|
|
92
|
+
"suggest": [],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"location": [
|
|
96
|
+
{
|
|
97
|
+
"pointer": "#/workflows/0/steps/0/onFailure/0/type",
|
|
98
|
+
"reportOnKey": false,
|
|
99
|
+
"source": "arazzo.yaml",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
"message": "The \`end\` type action is not supported by Spot.",
|
|
103
|
+
"ruleId": "no-actions-type-end",
|
|
104
|
+
"severity": "error",
|
|
105
|
+
"suggest": [],
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should not report when the type `end` action exists', async () => {
|
|
112
|
+
const results = await lintDocument({
|
|
113
|
+
externalRefResolver: new BaseResolver(),
|
|
114
|
+
document,
|
|
115
|
+
config: await makeConfig({
|
|
116
|
+
rules: {},
|
|
117
|
+
}),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { outdent } from 'outdent';
|
|
2
|
+
import { lintDocument } from '../../../lint';
|
|
3
|
+
import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
|
|
4
|
+
import { BaseResolver } from '../../../resolve';
|
|
5
|
+
|
|
6
|
+
describe('Arazzo no-criteria-xpath', () => {
|
|
7
|
+
const document = parseYamlToDocument(
|
|
8
|
+
outdent`
|
|
9
|
+
arazzo: '1.0.0'
|
|
10
|
+
info:
|
|
11
|
+
title: Cool API
|
|
12
|
+
version: 1.0.0
|
|
13
|
+
description: A cool API
|
|
14
|
+
sourceDescriptions:
|
|
15
|
+
- name: museum-api
|
|
16
|
+
type: openapi
|
|
17
|
+
url: openapi.yaml
|
|
18
|
+
workflows:
|
|
19
|
+
- workflowId: get-museum-hours
|
|
20
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
21
|
+
parameters:
|
|
22
|
+
- in: header
|
|
23
|
+
name: Authorization
|
|
24
|
+
value: Basic Og==
|
|
25
|
+
steps:
|
|
26
|
+
- stepId: create-event
|
|
27
|
+
description: >-
|
|
28
|
+
Create a new special event.
|
|
29
|
+
operationPath: $sourceDescriptions.museum-api#/paths/~1special-events/post
|
|
30
|
+
requestBody:
|
|
31
|
+
payload:
|
|
32
|
+
name: 'Mermaid Treasure Identification and Analysis'
|
|
33
|
+
location: 'Under the seaaa 🦀 🎶 🌊.'
|
|
34
|
+
eventDescription: 'Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.'
|
|
35
|
+
dates:
|
|
36
|
+
- '2023-09-05'
|
|
37
|
+
- '2023-09-08'
|
|
38
|
+
price: 0
|
|
39
|
+
successCriteria:
|
|
40
|
+
- condition: $statusCode == 201
|
|
41
|
+
- context: $response.body
|
|
42
|
+
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
|
43
|
+
type:
|
|
44
|
+
type: jsonpath
|
|
45
|
+
version: draft-goessner-dispatch-jsonpath-00
|
|
46
|
+
- context: $response.body
|
|
47
|
+
condition: $.name == 'Orca Identification and Analysis'
|
|
48
|
+
type: xpath
|
|
49
|
+
- context: $response.body
|
|
50
|
+
condition: $.name == 'Mermaid Treasure Identification and Analysis'
|
|
51
|
+
type:
|
|
52
|
+
type: xpath
|
|
53
|
+
version: xpath-30
|
|
54
|
+
outputs:
|
|
55
|
+
createdEventId: $response.body.eventId
|
|
56
|
+
name: $response.body.name
|
|
57
|
+
- workflowId: get-museum-hours-2
|
|
58
|
+
description: This workflow demonstrates how to get the museum opening hours and buy tickets.
|
|
59
|
+
parameters:
|
|
60
|
+
- in: header
|
|
61
|
+
name: Authorization
|
|
62
|
+
value: Basic Og==
|
|
63
|
+
steps:
|
|
64
|
+
- stepId: get-museum-hours
|
|
65
|
+
description: >-
|
|
66
|
+
Get museum hours by resolving request details with getMuseumHours operationId from openapi.yaml description.
|
|
67
|
+
operationId: museum-api.getMuseumHours
|
|
68
|
+
successCriteria:
|
|
69
|
+
- condition: $statusCode == 200
|
|
70
|
+
`,
|
|
71
|
+
'arazzo.yaml'
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
it('should report when the `xpath` criteria exists', async () => {
|
|
75
|
+
const results = await lintDocument({
|
|
76
|
+
externalRefResolver: new BaseResolver(),
|
|
77
|
+
document,
|
|
78
|
+
config: await makeConfig({
|
|
79
|
+
rules: {},
|
|
80
|
+
arazzoRules: { 'no-criteria-xpath': 'error' },
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
"location": [
|
|
88
|
+
{
|
|
89
|
+
"pointer": "#/workflows/0/steps/0/successCriteria/2/type",
|
|
90
|
+
"reportOnKey": false,
|
|
91
|
+
"source": "arazzo.yaml",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
"message": "The \`xpath\` type criteria is not supported by Spot.",
|
|
95
|
+
"ruleId": "no-criteria-xpath",
|
|
96
|
+
"severity": "error",
|
|
97
|
+
"suggest": [],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"location": [
|
|
101
|
+
{
|
|
102
|
+
"pointer": "#/workflows/0/steps/0/successCriteria/3/type",
|
|
103
|
+
"reportOnKey": false,
|
|
104
|
+
"source": "arazzo.yaml",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
"message": "The \`xpath\` type criteria is not supported by Spot.",
|
|
108
|
+
"ruleId": "no-criteria-xpath",
|
|
109
|
+
"severity": "error",
|
|
110
|
+
"suggest": [],
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
`);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should not report when the `xpath` criteria exists', async () => {
|
|
117
|
+
const results = await lintDocument({
|
|
118
|
+
externalRefResolver: new BaseResolver(),
|
|
119
|
+
document,
|
|
120
|
+
config: await makeConfig({
|
|
121
|
+
rules: {},
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
|
|
126
|
+
});
|
|
127
|
+
});
|