@redocly/cli 1.0.0-beta.96

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.
Files changed (79) hide show
  1. package/README.md +39 -0
  2. package/bin/cli.js +3 -0
  3. package/lib/__mocks__/utils.d.ts +17 -0
  4. package/lib/__mocks__/utils.js +14 -0
  5. package/lib/__tests__/commands/bundle.test.d.ts +1 -0
  6. package/lib/__tests__/commands/bundle.test.js +92 -0
  7. package/lib/__tests__/commands/push-region.test.d.ts +1 -0
  8. package/lib/__tests__/commands/push-region.test.js +55 -0
  9. package/lib/__tests__/commands/push.test.d.ts +1 -0
  10. package/lib/__tests__/commands/push.test.js +153 -0
  11. package/lib/__tests__/utils.test.d.ts +1 -0
  12. package/lib/__tests__/utils.test.js +41 -0
  13. package/lib/assert-node-version.d.ts +1 -0
  14. package/lib/assert-node-version.js +10 -0
  15. package/lib/commands/bundle.d.ts +19 -0
  16. package/lib/commands/bundle.js +128 -0
  17. package/lib/commands/join.d.ts +7 -0
  18. package/lib/commands/join.js +421 -0
  19. package/lib/commands/lint.d.ts +11 -0
  20. package/lib/commands/lint.js +80 -0
  21. package/lib/commands/login.d.ts +6 -0
  22. package/lib/commands/login.js +28 -0
  23. package/lib/commands/preview-docs/index.d.ts +12 -0
  24. package/lib/commands/preview-docs/index.js +141 -0
  25. package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
  26. package/lib/commands/preview-docs/preview-server/hot.js +42 -0
  27. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  28. package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
  29. package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
  30. package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
  31. package/lib/commands/preview-docs/preview-server/server.js +85 -0
  32. package/lib/commands/push.d.ts +25 -0
  33. package/lib/commands/push.js +247 -0
  34. package/lib/commands/split/__tests__/index.test.d.ts +1 -0
  35. package/lib/commands/split/__tests__/index.test.js +70 -0
  36. package/lib/commands/split/index.d.ts +8 -0
  37. package/lib/commands/split/index.js +279 -0
  38. package/lib/commands/split/types.d.ts +37 -0
  39. package/lib/commands/split/types.js +52 -0
  40. package/lib/commands/stats.d.ts +5 -0
  41. package/lib/commands/stats.js +92 -0
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.js +269 -0
  44. package/lib/js-utils.d.ts +3 -0
  45. package/lib/js-utils.js +16 -0
  46. package/lib/types.d.ts +13 -0
  47. package/lib/types.js +5 -0
  48. package/lib/utils.d.ts +28 -0
  49. package/lib/utils.js +260 -0
  50. package/package.json +54 -0
  51. package/src/__mocks__/utils.ts +11 -0
  52. package/src/__tests__/commands/bundle.test.ts +120 -0
  53. package/src/__tests__/commands/push-region.test.ts +51 -0
  54. package/src/__tests__/commands/push.test.ts +156 -0
  55. package/src/__tests__/utils.test.ts +50 -0
  56. package/src/assert-node-version.ts +8 -0
  57. package/src/commands/bundle.ts +178 -0
  58. package/src/commands/join.ts +488 -0
  59. package/src/commands/lint.ts +110 -0
  60. package/src/commands/login.ts +19 -0
  61. package/src/commands/preview-docs/index.ts +188 -0
  62. package/src/commands/preview-docs/preview-server/default.hbs +24 -0
  63. package/src/commands/preview-docs/preview-server/hot.js +42 -0
  64. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  65. package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
  66. package/src/commands/preview-docs/preview-server/server.ts +91 -0
  67. package/src/commands/push.ts +355 -0
  68. package/src/commands/split/__tests__/fixtures/spec.json +70 -0
  69. package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
  70. package/src/commands/split/__tests__/index.test.ts +96 -0
  71. package/src/commands/split/index.ts +349 -0
  72. package/src/commands/split/types.ts +73 -0
  73. package/src/commands/stats.ts +115 -0
  74. package/src/index.ts +311 -0
  75. package/src/js-utils.ts +12 -0
  76. package/src/types.ts +13 -0
  77. package/src/utils.ts +300 -0
  78. package/tsconfig.json +9 -0
  79. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,355 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import fetch from 'node-fetch';
4
+ import { performance } from 'perf_hooks';
5
+ import { yellow, green, blue } from 'colorette';
6
+ import { createHash } from 'crypto';
7
+ import {
8
+ bundle,
9
+ Config,
10
+ loadConfig,
11
+ RedoclyClient,
12
+ IGNORE_FILE,
13
+ BundleOutputFormat,
14
+ getTotals,
15
+ slash,
16
+ Region,
17
+ getMergedConfig,
18
+ } from '@redocly/openapi-core';
19
+ import {
20
+ exitWithError,
21
+ printExecutionTime,
22
+ getFallbackEntryPointsOrExit,
23
+ pluralize,
24
+ dumpBundle,
25
+ } from '../utils';
26
+ import { promptClientToken } from './login';
27
+
28
+ const DEFAULT_VERSION = 'latest';
29
+
30
+ type PushArgs = {
31
+ entrypoint?: string;
32
+ destination?: string;
33
+ branchName?: string;
34
+ upsert?: boolean;
35
+ 'run-id'?: string;
36
+ region?: Region;
37
+ 'skip-decorator'?: string[];
38
+ };
39
+
40
+ export async function handlePush(argv: PushArgs): Promise<void> {
41
+ const config = await loadConfig();
42
+ const region = argv.region || config.region;
43
+ const client = new RedoclyClient(region);
44
+ const isAuthorized = await client.isAuthorizedWithRedoclyByRegion();
45
+ if (!isAuthorized) {
46
+ const clientToken = await promptClientToken(client.domain);
47
+ await client.login(clientToken);
48
+ }
49
+
50
+ const startedAt = performance.now();
51
+ const { destination, branchName, upsert } = argv;
52
+
53
+ if (
54
+ destination &&
55
+ !(validateDestination(destination) || validateDestinationWithoutOrganization(destination))
56
+ ) {
57
+ exitWithError(
58
+ `Destination argument value is not valid, please use the right format: ${yellow(
59
+ '<@organization-id/api-name@api-version>',
60
+ )}`,
61
+ );
62
+ }
63
+
64
+ const [organizationId, name, version] = getDestinationProps(destination, config.organization);
65
+
66
+ if (!organizationId) {
67
+ return exitWithError(
68
+ `No organization provided, please use the right format: ${yellow(
69
+ '<@organization-id/api-name@api-version>',
70
+ )} or specify the 'organization' field in the config file.`,
71
+ );
72
+ }
73
+ const entrypoint =
74
+ argv.entrypoint || (name && version && getApiEntrypoint({ name, version, config }));
75
+
76
+ if (name && version && !entrypoint) {
77
+ exitWithError(
78
+ `No entrypoint found that matches ${blue(
79
+ `${name}@${version}`,
80
+ )}. Please make sure you have provided the correct data in the config file.`,
81
+ );
82
+ }
83
+
84
+ const apis = entrypoint ? { [`${name}@${version}`]: { root: entrypoint } } : config.apis;
85
+
86
+ for (const [apiNameAndVersion, { root: entrypoint }] of Object.entries(apis)) {
87
+ const resolvedConfig = getMergedConfig(config, apiNameAndVersion);
88
+ resolvedConfig.lint.skipDecorators(argv['skip-decorator']);
89
+
90
+ const [name, version = DEFAULT_VERSION] = apiNameAndVersion.split('@');
91
+ try {
92
+ let rootFilePath = '';
93
+ const filePaths: string[] = [];
94
+ const filesToUpload = await collectFilesToUpload(entrypoint, resolvedConfig);
95
+ const filesHash = hashFiles(filesToUpload.files);
96
+
97
+ process.stdout.write(
98
+ `Uploading ${filesToUpload.files.length} ${pluralize(
99
+ 'file',
100
+ filesToUpload.files.length,
101
+ )}:\n`,
102
+ );
103
+
104
+ let uploaded = 0;
105
+
106
+ for (let file of filesToUpload.files) {
107
+ const { signedUploadUrl, filePath } = await client.registryApi.prepareFileUpload({
108
+ organizationId,
109
+ name,
110
+ version,
111
+ filesHash,
112
+ filename: file.keyOnS3,
113
+ isUpsert: upsert,
114
+ });
115
+
116
+ if (file.filePath === filesToUpload.root) {
117
+ rootFilePath = filePath;
118
+ }
119
+
120
+ filePaths.push(filePath);
121
+
122
+ process.stdout.write(
123
+ `Uploading ${file.contents ? 'bundle for ' : ''}${blue(file.filePath)}...`,
124
+ );
125
+
126
+ const uploadResponse = await uploadFileToS3(
127
+ signedUploadUrl,
128
+ file.contents || file.filePath,
129
+ );
130
+
131
+ const fileCounter = `(${++uploaded}/${filesToUpload.files.length})`;
132
+
133
+ if (!uploadResponse.ok) {
134
+ exitWithError(`✗ ${fileCounter}\nFile upload failed\n`);
135
+ }
136
+
137
+ process.stdout.write(green(`✓ ${fileCounter}\n`));
138
+ }
139
+
140
+ process.stdout.write('\n');
141
+
142
+ await client.registryApi.pushApi({
143
+ organizationId,
144
+ name,
145
+ version,
146
+ rootFilePath,
147
+ filePaths,
148
+ branch: branchName,
149
+ isUpsert: upsert,
150
+ });
151
+ } catch (error) {
152
+ if (error.message === 'ORGANIZATION_NOT_FOUND') {
153
+ exitWithError(`Organization ${blue(organizationId)} not found`);
154
+ }
155
+
156
+ if (error.message === 'API_VERSION_NOT_FOUND') {
157
+ exitWithError(`The definition version ${blue(name)}/${blue(
158
+ version,
159
+ )} does not exist in organization ${blue(organizationId)}!\n${yellow(
160
+ 'Suggestion:',
161
+ )} please use ${blue('-u')} or ${blue('--upsert')} to create definition.
162
+ `);
163
+ }
164
+
165
+ throw error;
166
+ }
167
+
168
+ process.stdout.write(
169
+ `Definition: ${blue(entrypoint!)} is successfully pushed to Redocly API Registry \n`,
170
+ );
171
+ }
172
+ printExecutionTime('push', startedAt, entrypoint || `apis in organization ${organizationId}`);
173
+ }
174
+
175
+ function getFilesList(dir: string, files?: any): string[] {
176
+ files = files || [];
177
+ const filesAndDirs = fs.readdirSync(dir);
178
+ for (const name of filesAndDirs) {
179
+ if (fs.statSync(path.join(dir, name)).isDirectory()) {
180
+ files = getFilesList(path.join(dir, name), files);
181
+ } else {
182
+ const currentPath = dir + '/' + name;
183
+ files.push(currentPath);
184
+ }
185
+ }
186
+ return files;
187
+ }
188
+
189
+ async function collectFilesToUpload(entrypoint: string, config: Config) {
190
+ let files: { filePath: string; keyOnS3: string; contents?: Buffer }[] = [];
191
+ const [{ path: entrypointPath }] = await getFallbackEntryPointsOrExit([entrypoint], config);
192
+
193
+ process.stdout.write('Bundling definition\n');
194
+
195
+ const { bundle: openapiBundle, problems } = await bundle({
196
+ config,
197
+ ref: entrypointPath,
198
+ skipRedoclyRegistryRefs: true,
199
+ });
200
+
201
+ const fileTotals = getTotals(problems);
202
+
203
+ if (fileTotals.errors === 0) {
204
+ process.stdout.write(
205
+ `Created a bundle for ${blue(entrypoint)} ${
206
+ fileTotals.warnings > 0 ? 'with warnings' : ''
207
+ }\n`,
208
+ );
209
+ } else {
210
+ exitWithError(`Failed to create a bundle for ${blue(entrypoint)}\n`);
211
+ }
212
+
213
+ const fileExt = path.extname(entrypointPath).split('.').pop();
214
+ files.push(
215
+ getFileEntry(entrypointPath, dumpBundle(openapiBundle.parsed, fileExt as BundleOutputFormat)),
216
+ );
217
+
218
+ if (fs.existsSync('package.json')) {
219
+ files.push(getFileEntry('package.json'));
220
+ }
221
+ if (fs.existsSync(IGNORE_FILE)) {
222
+ files.push(getFileEntry(IGNORE_FILE));
223
+ }
224
+ if (config.configFile) {
225
+ // All config file paths including the root one
226
+ files.push(...[...new Set(config.lint.extendPaths)].map((f) => getFileEntry(f)));
227
+ if (config['features.openapi'].htmlTemplate) {
228
+ const dir = getFolder(config['features.openapi'].htmlTemplate);
229
+ const fileList = getFilesList(dir, []);
230
+ files.push(...fileList.map((f) => getFileEntry(f)));
231
+ }
232
+ let pluginFiles = new Set<string>();
233
+ for (const plugin of config.lint.pluginPaths) {
234
+ if (typeof plugin !== 'string') continue;
235
+ const fileList = getFilesList(getFolder(plugin), []);
236
+ fileList.forEach((f) => pluginFiles.add(f));
237
+ }
238
+ files.push(...filterPluginFilesByExt(Array.from(pluginFiles)).map((f) => getFileEntry(f)));
239
+ }
240
+ return {
241
+ files,
242
+ root: path.resolve(entrypointPath),
243
+ };
244
+
245
+ function filterPluginFilesByExt(files: string[]) {
246
+ return files.filter((file: string) => {
247
+ const fileExt = path.extname(file).toLowerCase();
248
+ return fileExt === '.js' || fileExt === '.ts' || fileExt === '.mjs' || fileExt === 'json';
249
+ });
250
+ }
251
+
252
+ function getFileEntry(filename: string, contents?: string) {
253
+ return {
254
+ filePath: path.resolve(filename),
255
+ keyOnS3: config.configFile
256
+ ? slash(path.relative(path.dirname(config.configFile), filename))
257
+ : slash(path.basename(filename)),
258
+ contents: (contents && Buffer.from(contents, 'utf-8')) || undefined,
259
+ };
260
+ }
261
+ }
262
+
263
+ function getFolder(filePath: string) {
264
+ return path.resolve(path.dirname(filePath));
265
+ }
266
+
267
+ function hashFiles(filePaths: { filePath: string }[]) {
268
+ let sum = createHash('sha256');
269
+ filePaths.forEach((file) => sum.update(fs.readFileSync(file.filePath)));
270
+ return sum.digest('hex');
271
+ }
272
+
273
+ function validateDestination(destination: string) {
274
+ const regexp = /^@+([a-zA-Z0-9-_.& ]+)\/+([^@\/]+)@([^@\/]+)$/;
275
+ return regexp.test(destination);
276
+ }
277
+
278
+ function validateDestinationWithoutOrganization(destination: string) {
279
+ const regexp = /^()([^@\/]+)@([^@\/]+)$/;
280
+ return regexp.test(destination);
281
+ }
282
+
283
+ export function getDestinationProps(
284
+ destination: string | undefined,
285
+ organization: string | undefined,
286
+ ) {
287
+ return destination && validateDestination(destination)
288
+ ? destination.substring(1).split(/[@\/]/)
289
+ : destination && validateDestinationWithoutOrganization(destination)
290
+ ? [organization, ...destination.split('@')]
291
+ : [organization];
292
+ }
293
+
294
+ type BarePushArgs = Omit<PushArgs, 'entrypoint' | 'destination' | 'branchName'> & {
295
+ maybeEntrypointOrAliasOrDestination?: string;
296
+ maybeDestination?: string;
297
+ maybeBranchName?: string;
298
+ branch?: string;
299
+ };
300
+
301
+ export const transformPush =
302
+ (callback: typeof handlePush) =>
303
+ ({
304
+ maybeEntrypointOrAliasOrDestination,
305
+ maybeDestination,
306
+ maybeBranchName,
307
+ branch,
308
+ ...rest
309
+ }: BarePushArgs) => {
310
+ if (!!maybeBranchName) {
311
+ process.stderr.write(
312
+ yellow(
313
+ 'Deprecation warning: Do not use the third parameter as a branch name. Please use a separate --branch option instead.',
314
+ ),
315
+ );
316
+ }
317
+ const entrypoint = maybeDestination ? maybeEntrypointOrAliasOrDestination : undefined;
318
+ const destination = maybeDestination || maybeEntrypointOrAliasOrDestination;
319
+ return callback({
320
+ ...rest,
321
+ destination,
322
+ entrypoint,
323
+ branchName: branch ?? maybeBranchName,
324
+ });
325
+ };
326
+
327
+ export function getApiEntrypoint({
328
+ name,
329
+ version,
330
+ config: { apis },
331
+ }: {
332
+ name: string;
333
+ version: string;
334
+ config: Config;
335
+ }): string {
336
+ const api = apis?.[`${name}@${version}`] || (version === DEFAULT_VERSION && apis?.[name]);
337
+ return api?.root;
338
+ }
339
+
340
+ function uploadFileToS3(url: string, filePathOrBuffer: string | Buffer) {
341
+ const fileSizeInBytes =
342
+ typeof filePathOrBuffer === 'string'
343
+ ? fs.statSync(filePathOrBuffer).size
344
+ : filePathOrBuffer.byteLength;
345
+ let readStream =
346
+ typeof filePathOrBuffer === 'string' ? fs.createReadStream(filePathOrBuffer) : filePathOrBuffer;
347
+
348
+ return fetch(url, {
349
+ method: 'PUT',
350
+ headers: {
351
+ 'Content-Length': fileSizeInBytes.toString(),
352
+ },
353
+ body: readStream,
354
+ });
355
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "openapi": "3.0.1",
3
+ "info": {
4
+ "title": "TEST",
5
+ "description": "TEST",
6
+ "version": "v1",
7
+ "license": {
8
+ "name": "Apache 2.0",
9
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
10
+ }
11
+ },
12
+ "servers": [
13
+ {
14
+ "url": "http://petstore.swagger.io/v1"
15
+ }
16
+ ],
17
+ "components": {
18
+ "schemas": {
19
+ "Test": {
20
+ "nullable": true
21
+ }
22
+ }
23
+ },
24
+ "paths": {
25
+ "/test": {
26
+ "get": {
27
+ "summary": "test",
28
+ "operationId": "test",
29
+ "responses": {
30
+ "202": {
31
+ "description": "Test",
32
+ "content": {
33
+ "application/json": {
34
+ "schema": {
35
+ "$ref": "#/components/schemas/Test"
36
+ },
37
+ "example": {}
38
+ }
39
+ }
40
+ },
41
+ "400": {
42
+ "description": "An error response"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ },
48
+ "x-webhooks": {
49
+ "test": {
50
+ "post": {
51
+ "summary": "New pet",
52
+ "description": "Information about a new pet in the systems",
53
+ "operationId": "newPet",
54
+ "tags": ["pet"],
55
+ "requestBody": {
56
+ "content": {
57
+ "application/json": {
58
+ "schema": { "$ref": "#/components/schemas/Test" }
59
+ }
60
+ }
61
+ },
62
+ "responses": {
63
+ "200": {
64
+ "description": "Return a 200 status to indicate that the data was received successfully"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": {
4
+ "title": "Webhook Example",
5
+ "version": "1.0.0"
6
+ },
7
+ "paths": {
8
+ "/pets": {
9
+ "get": {
10
+ "summary": "List all pets",
11
+ "operationId": "listPets",
12
+ "parameters": [
13
+ {
14
+ "name": "limit",
15
+ "in": "query",
16
+ "description": "How many items to return at one time (max 100)",
17
+ "required": false,
18
+ "schema": {
19
+ "type": "integer",
20
+ "format": "int32"
21
+ }
22
+ }
23
+ ],
24
+ "responses": {
25
+ "200": {
26
+ "description": "A paged array of pets",
27
+ "content": {
28
+ "application/json": {
29
+ "schema": {
30
+ "$ref": "#/components/schemas/Pets"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ },
39
+ "webhooks": {
40
+ "test": {
41
+ "post": {
42
+ "requestBody": {
43
+ "description": "Information about a new pet in the system",
44
+ "content": {
45
+ "application/json": {
46
+ "schema": {
47
+ "$ref": "#/components/schemas/Pet"
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "responses": {
53
+ "200": {
54
+ "description": "Return a 200 status to indicate that the data was received successfully"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "components": {
61
+ "schemas": {
62
+ "Pet": {
63
+ "required": [
64
+ "id",
65
+ "name"
66
+ ],
67
+ "properties": {
68
+ "id": {
69
+ "type": "integer",
70
+ "format": "int64"
71
+ },
72
+ "name": {
73
+ "type": "string"
74
+ },
75
+ "tag": {
76
+ "type": "string"
77
+ }
78
+ }
79
+ },
80
+ "Pets": {
81
+ "type": "array",
82
+ "items": {
83
+ "$ref": "#/components/schemas/Pet"
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,96 @@
1
+ import { iteratePathItems, handleSplit } from '../index';
2
+ import * as path from 'path';
3
+ import * as openapiCore from '@redocly/openapi-core';
4
+ import {
5
+ ComponentsFiles,
6
+ } from '../types';
7
+ import { blue, green } from 'colorette';
8
+
9
+ jest.mock('../../../utils', () => ({
10
+ ...jest.requireActual('../../../utils'),
11
+ writeYaml: jest.fn(),
12
+ }));
13
+
14
+ jest.mock('@redocly/openapi-core', () => ({
15
+ ...jest.requireActual('@redocly/openapi-core'),
16
+ isRef: jest.fn(),
17
+ }));
18
+
19
+ describe('#split', () => {
20
+ const openapiDir = 'test';
21
+ const componentsFiles: ComponentsFiles = {};
22
+
23
+ it('should split the file and show the success message', async () => {
24
+ const filePath = "./packages/cli/src/commands/split/__tests__/fixtures/spec.json";
25
+ jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
26
+
27
+ await handleSplit (
28
+ {
29
+ entrypoint: filePath,
30
+ outDir: openapiDir,
31
+ separator: '_',
32
+ }
33
+ );
34
+
35
+ expect(process.stderr.write).toBeCalledTimes(2);
36
+ expect((process.stderr.write as jest.Mock).mock.calls[0][0]).toBe(
37
+ `🪓 Document: ${blue(filePath!)} ${green('is successfully split')}
38
+ and all related files are saved to the directory: ${blue(openapiDir)} \n`
39
+ );
40
+ expect((process.stderr.write as jest.Mock).mock.calls[1][0]).toContain(
41
+ `${filePath}: split processed in <test>ms`
42
+ );
43
+ });
44
+
45
+
46
+ it('should use the correct separator', async () => {
47
+ const filePath = "./packages/cli/src/commands/split/__tests__/fixtures/spec.json";
48
+
49
+ const utils = require('../../../utils');
50
+ jest.spyOn(utils, 'pathToFilename').mockImplementation(() => 'newFilePath');
51
+
52
+ await handleSplit (
53
+ {
54
+ entrypoint: filePath,
55
+ outDir: openapiDir,
56
+ separator: '_',
57
+ }
58
+ );
59
+
60
+ expect(utils.pathToFilename).toBeCalledWith(expect.anything(), '_');
61
+ utils.pathToFilename.mockRestore();
62
+ });
63
+
64
+ it('should have correct path with paths', () => {
65
+ const openapi = require("./fixtures/spec.json");
66
+
67
+ jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'paths/test.yaml');
68
+ jest.spyOn(path, 'relative').mockImplementation(() => 'paths/test.yaml');
69
+ iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles, '_');
70
+
71
+ expect(openapiCore.slash).toHaveBeenCalledWith('paths/test.yaml');
72
+ expect(path.relative).toHaveBeenCalledWith('test', 'test/paths/test.yaml');
73
+ });
74
+
75
+ it('should have correct path with webhooks', () => {
76
+ const openapi = require("./fixtures/webhooks.json");
77
+
78
+ jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
79
+ jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
80
+ iteratePathItems(openapi.webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
81
+
82
+ expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
83
+ expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
84
+ });
85
+
86
+ it('should have correct path with x-webhooks', () => {
87
+ const openapi = require("./fixtures/spec.json");
88
+
89
+ jest.spyOn(openapiCore, 'slash').mockImplementation(() => 'webhooks/test.yaml');
90
+ jest.spyOn(path, 'relative').mockImplementation(() => 'webhooks/test.yaml');
91
+ iteratePathItems(openapi['x-webhooks'], openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, 'webhook_');
92
+
93
+ expect(openapiCore.slash).toHaveBeenCalledWith('webhooks/test.yaml');
94
+ expect(path.relative).toHaveBeenCalledWith('test', 'test/webhooks/test.yaml');
95
+ });
96
+ });