mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] Allow to specify custom servers
and security
for the result OpenAPI bundle (#189348)
**Resolves:** https://github.com/elastic/kibana/issues/189269 **Resolves:** https://github.com/elastic/kibana/issues/189270 ## Summary This PR adds an ability to specify OpenAPI `servers` and security requirements (`security`) to be used in the result bundle. `servers` and/or `security` in the source OpenAPI specs are be dropped when custom `servers` and/or `security` provided. ## Details Kibana is usually deployed at a single access point and manages authentication in a central way. That way it's much more convenient to have control on what `servers` and `security` are present in the result bundles. It will help to avoid conflicts, duplicates and update them in centralized way. This PR extends OpenAPI bundler configuration options with `prototypeDocument`. "Prototype" in the name means it's a prototype for the result. The bundler uses certain properties from that prototype OpenAPI document to add them to the result OpenAPI bundle. The following properties are used - `info` representing OpenAPI Info object (former `options.specInfo`) - `servers` OpenAPI Server Object Array - `security` + `components.securitySchemes` OpenAPI Security Requirement Object Array + OpenAPI Security Schemes Object (validation checks that both fields are set otherwise an error is thrown) For convenience `prototypeDocument` could be specified as a string path to a file containing prototype OpenAPI document. ## How to test? `prototypeDocument` can be specified for `bundle` and `merge` utilities like the following **bundle** ```js const { bundle } = require('@kbn/openapi-bundler'); (async () => { await bundle({ sourceGlob: 'source/glob/*.yaml', outputFilePath: 'output/bundle.yaml, options: { prototypeDocument: { info: { title: 'Some title', description: 'Some description', }, servers: [{ url: 'https://{kibana_url}', variables: { kibana_url: { default: 'localhost:5601', } } }], security: [{ ApiKeyAuth: [] }], components: { securitySchemes: { ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'Authorization', } } } }, }, }); ``` **bundle** with external prototype document ```js const { bundle } = require('@kbn/openapi-bundler'); (async () => { await bundle({ sourceGlob: 'source/glob/*.yaml', outputFilePath: 'output/bundle.yaml, options: { prototypeDocument: 'path/to/prototype_document.yaml',, }, }); ``` **merge** ```js const { merge } = require('@kbn/openapi-bundler'); (async () => { await merge({ sourceGlobs: [ 'absolute/path/to/file.yaml`, 'some/glob/*.schema.yaml', ], outputFilePath: 'output/file/path/bundle.yaml', options: { prototypeDocument: { info: { title: 'Some title', description: 'Some description', }, servers: [{ url: 'https://{kibana_url}', variables: { kibana_url: { default: 'localhost:5601', } } }], security: [{ ApiKeyAuth: [] }], components: { securitySchemes: { ApiKeyAuth: { type: 'apiKey', in: 'header', name: 'Authorization', } } } }, }, }); })(); ``` **merge** with external prototype document ```js const { merge } = require('@kbn/openapi-bundler'); (async () => { await merge({ sourceGlobs: [ 'absolute/path/to/file.yaml`, 'some/glob/*.schema.yaml', ], outputFilePath: 'output/file/path/bundle.yaml', options: { prototypeDocument: 'path/to/prototype_document.yaml', }, }); })(); ``` The result bundles will contain specified `servers` and `security` while source `servers` and `security` will be dropped.
This commit is contained in:
parent
9dcd8aad88
commit
51c8949af9
22 changed files with 1964 additions and 122 deletions
|
@ -10,12 +10,32 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
|
||||
export function createBlankOpenApiDocument(
|
||||
oasVersion: string,
|
||||
info: OpenAPIV3.InfoObject
|
||||
overrides?: Partial<OpenAPIV3.Document>
|
||||
): OpenAPIV3.Document {
|
||||
return {
|
||||
openapi: oasVersion,
|
||||
info,
|
||||
servers: [
|
||||
info: overrides?.info ?? {
|
||||
title: 'Merged OpenAPI specs',
|
||||
version: 'not specified',
|
||||
},
|
||||
paths: overrides?.paths ?? {},
|
||||
components: {
|
||||
schemas: overrides?.components?.schemas,
|
||||
responses: overrides?.components?.responses,
|
||||
parameters: overrides?.components?.parameters,
|
||||
examples: overrides?.components?.examples,
|
||||
requestBodies: overrides?.components?.requestBodies,
|
||||
headers: overrides?.components?.headers,
|
||||
securitySchemes: overrides?.components?.securitySchemes ?? {
|
||||
BasicAuth: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
links: overrides?.components?.links,
|
||||
callbacks: overrides?.components?.callbacks,
|
||||
},
|
||||
servers: overrides?.servers ?? [
|
||||
{
|
||||
url: 'http://{kibana_host}:{port}',
|
||||
variables: {
|
||||
|
@ -28,19 +48,12 @@ export function createBlankOpenApiDocument(
|
|||
},
|
||||
},
|
||||
],
|
||||
security: [
|
||||
security: overrides?.security ?? [
|
||||
{
|
||||
BasicAuth: [],
|
||||
},
|
||||
],
|
||||
paths: {},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
BasicAuth: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: overrides?.tags,
|
||||
externalDocs: overrides?.externalDocs,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ import { mergeTags } from './merge_tags';
|
|||
import { getOasVersion } from '../../utils/get_oas_version';
|
||||
import { getOasDocumentVersion } from '../../utils/get_oas_document_version';
|
||||
import { enrichWithVersionMimeParam } from './enrich_with_version_mime_param';
|
||||
import { MergeOptions } from './merge_options';
|
||||
|
||||
export interface MergeDocumentsOptions {
|
||||
interface MergeDocumentsOptions extends MergeOptions {
|
||||
splitDocumentsByVersion: boolean;
|
||||
}
|
||||
|
||||
|
@ -52,10 +53,20 @@ export async function mergeDocuments(
|
|||
...documentsGroup,
|
||||
];
|
||||
|
||||
mergedDocument.servers = mergeServers(documentsToMerge);
|
||||
mergedDocument.paths = mergePaths(documentsToMerge);
|
||||
mergedDocument.components = mergeSharedComponents(documentsToMerge);
|
||||
mergedDocument.security = mergeSecurityRequirements(documentsToMerge);
|
||||
mergedDocument.paths = mergePaths(documentsToMerge, options);
|
||||
mergedDocument.components = {
|
||||
...mergedDocument.components,
|
||||
...mergeSharedComponents(documentsToMerge, options),
|
||||
};
|
||||
|
||||
if (!options.skipServers) {
|
||||
mergedDocument.servers = mergeServers(documentsToMerge);
|
||||
}
|
||||
|
||||
if (!options.skipSecurity) {
|
||||
mergedDocument.security = mergeSecurityRequirements(documentsToMerge);
|
||||
}
|
||||
|
||||
mergedDocument.tags = mergeTags(documentsToMerge);
|
||||
|
||||
mergedByVersion.set(mergedDocument.info.version, mergedDocument);
|
||||
|
|
|
@ -11,10 +11,12 @@ import deepEqual from 'fast-deep-equal';
|
|||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { KNOWN_HTTP_METHODS } from './http_methods';
|
||||
import { isRefNode } from '../process_document';
|
||||
import { MergeOptions } from './merge_options';
|
||||
|
||||
export function mergeOperations(
|
||||
sourcePathItem: OpenAPIV3.PathItemObject,
|
||||
mergedPathItem: OpenAPIV3.PathItemObject
|
||||
mergedPathItem: OpenAPIV3.PathItemObject,
|
||||
options: MergeOptions
|
||||
) {
|
||||
for (const httpMethod of KNOWN_HTTP_METHODS) {
|
||||
const sourceOperation = sourcePathItem[httpMethod];
|
||||
|
@ -24,12 +26,18 @@ export function mergeOperations(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!mergedOperation || deepEqual(sourceOperation, mergedOperation)) {
|
||||
mergedPathItem[httpMethod] = sourceOperation;
|
||||
const normalizedSourceOperation = {
|
||||
...sourceOperation,
|
||||
...(options.skipServers ? { servers: undefined } : { servers: sourceOperation.servers }),
|
||||
...(options.skipSecurity ? { security: undefined } : { security: sourceOperation.security }),
|
||||
};
|
||||
|
||||
if (!mergedOperation || deepEqual(normalizedSourceOperation, mergedOperation)) {
|
||||
mergedPathItem[httpMethod] = normalizedSourceOperation;
|
||||
continue;
|
||||
}
|
||||
|
||||
mergeOperation(sourceOperation, mergedOperation);
|
||||
mergeOperation(normalizedSourceOperation, mergedOperation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export interface MergeOptions {
|
||||
skipServers: boolean;
|
||||
skipSecurity: boolean;
|
||||
}
|
|
@ -12,8 +12,12 @@ import { ResolvedDocument } from '../ref_resolver/resolved_document';
|
|||
import { isRefNode } from '../process_document';
|
||||
import { mergeOperations } from './merge_operations';
|
||||
import { mergeArrays } from './merge_arrays';
|
||||
import { MergeOptions } from './merge_options';
|
||||
|
||||
export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.PathsObject {
|
||||
export function mergePaths(
|
||||
resolvedDocuments: ResolvedDocument[],
|
||||
options: MergeOptions
|
||||
): OpenAPIV3.PathsObject {
|
||||
const mergedPaths: Record<string, OpenAPIV3.PathItemObject> = {};
|
||||
|
||||
for (const { absolutePath, document } of resolvedDocuments) {
|
||||
|
@ -60,7 +64,7 @@ export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.Pat
|
|||
}
|
||||
|
||||
try {
|
||||
mergeOperations(sourcePathItem, mergedPathItem);
|
||||
mergeOperations(sourcePathItem, mergedPathItem, options);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`❌ Unable to merge ${chalk.bold(absolutePath)} due to an error in ${chalk.bold(
|
||||
|
@ -69,7 +73,9 @@ export function mergePaths(resolvedDocuments: ResolvedDocument[]): OpenAPIV3.Pat
|
|||
);
|
||||
}
|
||||
|
||||
mergePathItemServers(sourcePathItem, mergedPathItem);
|
||||
if (!options.skipServers) {
|
||||
mergePathItemServers(sourcePathItem, mergedPathItem);
|
||||
}
|
||||
|
||||
try {
|
||||
mergeParameters(sourcePathItem, mergedPathItem);
|
||||
|
|
|
@ -12,6 +12,7 @@ import { OpenAPIV3 } from 'openapi-types';
|
|||
import { ResolvedDocument } from '../ref_resolver/resolved_document';
|
||||
import { extractObjectByJsonPointer } from '../../utils/extract_by_json_pointer';
|
||||
import { logger } from '../../logger';
|
||||
import { MergeOptions } from './merge_options';
|
||||
|
||||
const MERGEABLE_COMPONENT_TYPES = [
|
||||
'schemas',
|
||||
|
@ -26,11 +27,16 @@ const MERGEABLE_COMPONENT_TYPES = [
|
|||
] as const;
|
||||
|
||||
export function mergeSharedComponents(
|
||||
bundledDocuments: ResolvedDocument[]
|
||||
bundledDocuments: ResolvedDocument[],
|
||||
options: MergeOptions
|
||||
): OpenAPIV3.ComponentsObject {
|
||||
const mergedComponents: Record<string, unknown> = {};
|
||||
|
||||
for (const componentsType of MERGEABLE_COMPONENT_TYPES) {
|
||||
if (options.skipSecurity && componentsType === 'securitySchemes') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mergedTypedComponents = mergeObjects(bundledDocuments, `/components/${componentsType}`);
|
||||
|
||||
if (Object.keys(mergedTypedComponents).length === 0) {
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { isUndefined, omitBy } from 'lodash';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { basename, dirname } from 'path';
|
||||
import { bundleDocument, SkipException } from './bundler/bundle_document';
|
||||
import { mergeDocuments } from './bundler/merge_documents';
|
||||
|
@ -19,6 +17,8 @@ import { writeDocuments } from './utils/write_documents';
|
|||
import { ResolvedDocument } from './bundler/ref_resolver/resolved_document';
|
||||
import { resolveGlobs } from './utils/resolve_globs';
|
||||
import { DEFAULT_BUNDLING_PROCESSORS, withIncludeLabelsProcessor } from './bundler/processor_sets';
|
||||
import { PrototypeDocument } from './prototype_document';
|
||||
import { validatePrototypeDocument } from './validate_prototype_document';
|
||||
|
||||
export interface BundlerConfig {
|
||||
sourceGlob: string;
|
||||
|
@ -27,8 +27,15 @@ export interface BundlerConfig {
|
|||
}
|
||||
|
||||
interface BundleOptions {
|
||||
/**
|
||||
* OpenAPI document itself or path to the document
|
||||
*/
|
||||
prototypeDocument?: PrototypeDocument | string;
|
||||
/**
|
||||
* When specified the produced bundle will contain only
|
||||
* operations objects with matching labels
|
||||
*/
|
||||
includeLabels?: string[];
|
||||
specInfo?: Omit<Partial<OpenAPIV3.InfoObject>, 'version'>;
|
||||
}
|
||||
|
||||
export const bundle = async ({
|
||||
|
@ -36,6 +43,10 @@ export const bundle = async ({
|
|||
outputFilePath = 'bundled-{version}.schema.yaml',
|
||||
options,
|
||||
}: BundlerConfig) => {
|
||||
const prototypeDocument = options?.prototypeDocument
|
||||
? await validatePrototypeDocument(options?.prototypeDocument)
|
||||
: undefined;
|
||||
|
||||
logger.debug(chalk.bold(`Bundling API route schemas`));
|
||||
logger.debug(`👀 Searching for source files in ${chalk.underline(sourceGlob)}`);
|
||||
|
||||
|
@ -56,22 +67,21 @@ export const bundle = async ({
|
|||
|
||||
logger.success(`Processed ${bundledDocuments.length} schemas`);
|
||||
|
||||
const blankOasFactory = (oasVersion: string, apiVersion: string) =>
|
||||
const blankOasDocumentFactory = (oasVersion: string, apiVersion: string) =>
|
||||
createBlankOpenApiDocument(oasVersion, {
|
||||
version: apiVersion,
|
||||
title: options?.specInfo?.title ?? 'Bundled OpenAPI specs',
|
||||
...omitBy(
|
||||
{
|
||||
description: options?.specInfo?.description,
|
||||
termsOfService: options?.specInfo?.termsOfService,
|
||||
contact: options?.specInfo?.contact,
|
||||
license: options?.specInfo?.license,
|
||||
},
|
||||
isUndefined
|
||||
),
|
||||
info: prototypeDocument?.info
|
||||
? { ...DEFAULT_INFO, ...prototypeDocument.info, version: apiVersion }
|
||||
: { ...DEFAULT_INFO, version: apiVersion },
|
||||
servers: prototypeDocument?.servers,
|
||||
security: prototypeDocument?.security,
|
||||
components: {
|
||||
securitySchemes: prototypeDocument?.components?.securitySchemes,
|
||||
},
|
||||
});
|
||||
const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasFactory, {
|
||||
const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasDocumentFactory, {
|
||||
splitDocumentsByVersion: true,
|
||||
skipServers: Boolean(prototypeDocument?.servers),
|
||||
skipSecurity: Boolean(prototypeDocument?.security),
|
||||
});
|
||||
|
||||
await writeDocuments(resultDocumentsMap, outputFilePath);
|
||||
|
@ -130,3 +140,7 @@ function filterOutSkippedDocuments(
|
|||
|
||||
return processedDocuments;
|
||||
}
|
||||
|
||||
const DEFAULT_INFO = {
|
||||
title: 'Bundled OpenAPI specs',
|
||||
} as const;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
import { mergeDocuments } from './bundler/merge_documents';
|
||||
import { logger } from './logger';
|
||||
import { createBlankOpenApiDocument } from './bundler/merge_documents/create_blank_oas_document';
|
||||
|
@ -16,13 +16,20 @@ import { writeDocuments } from './utils/write_documents';
|
|||
import { resolveGlobs } from './utils/resolve_globs';
|
||||
import { bundleDocument } from './bundler/bundle_document';
|
||||
import { withNamespaceComponentsProcessor } from './bundler/processor_sets';
|
||||
import { PrototypeDocument } from './prototype_document';
|
||||
import { validatePrototypeDocument } from './validate_prototype_document';
|
||||
|
||||
export interface MergerConfig {
|
||||
sourceGlobs: string[];
|
||||
outputFilePath: string;
|
||||
options?: {
|
||||
mergedSpecInfo?: Partial<OpenAPIV3.InfoObject>;
|
||||
};
|
||||
options?: MergerOptions;
|
||||
}
|
||||
|
||||
interface MergerOptions {
|
||||
/**
|
||||
* OpenAPI document itself or path to the document
|
||||
*/
|
||||
prototypeDocument?: PrototypeDocument | string;
|
||||
}
|
||||
|
||||
export const merge = async ({
|
||||
|
@ -34,6 +41,10 @@ export const merge = async ({
|
|||
throw new Error('As minimum one source glob is expected');
|
||||
}
|
||||
|
||||
const prototypeDocument = options?.prototypeDocument
|
||||
? await validatePrototypeDocument(options?.prototypeDocument)
|
||||
: undefined;
|
||||
|
||||
logger.info(chalk.bold(`Merging OpenAPI specs`));
|
||||
logger.info(
|
||||
`👀 Searching for source files in ${sourceGlobs
|
||||
|
@ -52,13 +63,18 @@ export const merge = async ({
|
|||
|
||||
const blankOasDocumentFactory = (oasVersion: string) =>
|
||||
createBlankOpenApiDocument(oasVersion, {
|
||||
title: 'Merged OpenAPI specs',
|
||||
version: 'not specified',
|
||||
...(options?.mergedSpecInfo ?? {}),
|
||||
info: prototypeDocument?.info ? { ...DEFAULT_INFO, ...prototypeDocument.info } : DEFAULT_INFO,
|
||||
servers: prototypeDocument?.servers,
|
||||
security: prototypeDocument?.security,
|
||||
components: {
|
||||
securitySchemes: prototypeDocument?.components?.securitySchemes,
|
||||
},
|
||||
});
|
||||
|
||||
const resultDocumentsMap = await mergeDocuments(bundledDocuments, blankOasDocumentFactory, {
|
||||
splitDocumentsByVersion: false,
|
||||
skipServers: Boolean(prototypeDocument?.servers),
|
||||
skipSecurity: Boolean(prototypeDocument?.security),
|
||||
});
|
||||
// Only one document is expected when `splitDocumentsByVersion` is set to `false`
|
||||
const mergedDocument = Array.from(resultDocumentsMap.values())[0];
|
||||
|
@ -80,3 +96,8 @@ async function bundleDocuments(schemaFilePaths: string[]): Promise<ResolvedDocum
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
const DEFAULT_INFO = {
|
||||
title: 'Merged OpenAPI specs',
|
||||
version: 'not specified',
|
||||
} as const;
|
||||
|
|
29
packages/kbn-openapi-bundler/src/prototype_document.ts
Normal file
29
packages/kbn-openapi-bundler/src/prototype_document.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
/**
|
||||
* `PrototypeDocument` is used as a prototype for the result file. In the other words
|
||||
* it provides a way to specify the following properties
|
||||
*
|
||||
* - `info` info object
|
||||
* - `servers` servers used to replace `servers` in the source OpenAPI specs
|
||||
* - `security` security requirements used to replace `security` in the source OpenAPI specs
|
||||
* It must be specified together with `components.securitySchemes`.
|
||||
*
|
||||
* All the other properties will be ignored.
|
||||
*/
|
||||
export interface PrototypeDocument {
|
||||
info?: Partial<OpenAPIV3.InfoObject>;
|
||||
servers?: OpenAPIV3.ServerObject[];
|
||||
security?: OpenAPIV3.SecurityRequirementObject[];
|
||||
components?: {
|
||||
securitySchemes: Record<string, OpenAPIV3.SecuritySchemeObject>;
|
||||
};
|
||||
}
|
|
@ -7,37 +7,48 @@
|
|||
*/
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import { load } from 'js-yaml';
|
||||
import { basename, extname } from 'path';
|
||||
import { load } from 'js-yaml';
|
||||
import chalk from 'chalk';
|
||||
import { logger } from '../logger';
|
||||
import { isPlainObjectType } from './is_plain_object_type';
|
||||
|
||||
export async function readDocument(documentPath: string): Promise<Record<string, unknown>> {
|
||||
const extension = extname(documentPath);
|
||||
|
||||
logger.debug(`Reading ${chalk.bold(basename(documentPath))}`);
|
||||
|
||||
const maybeDocument = await readFile(documentPath);
|
||||
|
||||
if (!isPlainObjectType(maybeDocument)) {
|
||||
throw new Error(`File at ${chalk.bold(documentPath)} is not valid OpenAPI document`);
|
||||
}
|
||||
|
||||
return maybeDocument;
|
||||
}
|
||||
|
||||
async function readFile(filePath: string): Promise<unknown> {
|
||||
const extension = extname(filePath);
|
||||
|
||||
switch (extension) {
|
||||
case '.yaml':
|
||||
case '.yml':
|
||||
return await readYamlDocument(documentPath);
|
||||
return await readYamlFile(filePath);
|
||||
|
||||
case '.json':
|
||||
return await readJsonDocument(documentPath);
|
||||
return await readJsonFile(filePath);
|
||||
|
||||
default:
|
||||
throw new Error(`${extension} files are not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
async function readYamlDocument(filePath: string): Promise<Record<string, unknown>> {
|
||||
async function readYamlFile(filePath: string): Promise<Record<string, unknown>> {
|
||||
// Typing load's result to Record<string, unknown> is optimistic as we can't be sure
|
||||
// there is object inside a yaml file. We don't have this validation layer so far
|
||||
// but using JSON Schemas here should mitigate this problem.
|
||||
return load(await fs.readFile(filePath, { encoding: 'utf8' }));
|
||||
}
|
||||
|
||||
export async function readJsonDocument(filePath: string): Promise<Record<string, unknown>> {
|
||||
async function readJsonFile(filePath: string): Promise<Record<string, unknown>> {
|
||||
// Typing load's result to Record<string, unknown> is optimistic as we can't be sure
|
||||
// there is object inside a yaml file. We don't have this validation layer so far
|
||||
// but using JSON Schemas here should mitigate this problem.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { PrototypeDocument } from './prototype_document';
|
||||
import { readDocument } from './utils/read_document';
|
||||
|
||||
/**
|
||||
* Validates that passed `prototypeDocument` fulfills the requirements.
|
||||
*
|
||||
* In particular security requirements must be specified via `security` and
|
||||
* `components.securitySchemes` properties.
|
||||
*
|
||||
*/
|
||||
export async function validatePrototypeDocument(
|
||||
prototypeDocumentOrString: PrototypeDocument | string
|
||||
): Promise<PrototypeDocument> {
|
||||
const prototypeDocument: PrototypeDocument | undefined =
|
||||
typeof prototypeDocumentOrString === 'string'
|
||||
? await readDocument(prototypeDocumentOrString)
|
||||
: prototypeDocumentOrString;
|
||||
|
||||
if (prototypeDocument.security && !prototypeDocument.components?.securitySchemes) {
|
||||
throw new Error(
|
||||
`Prototype document must contain ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} when security requirements are specified`
|
||||
);
|
||||
}
|
||||
|
||||
if (prototypeDocument.components?.securitySchemes && !prototypeDocument.security) {
|
||||
throw new Error(
|
||||
`Prototype document must have ${chalk.bold('security')} defined ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} are specified`
|
||||
);
|
||||
}
|
||||
|
||||
return prototypeDocument;
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { createOASDocument } from '../../create_oas_document';
|
||||
import { bundleSpecs } from '../bundle_specs';
|
||||
|
||||
describe('OpenAPI Bundler - with security requirements overrides', () => {
|
||||
describe('enabled', () => {
|
||||
it('throws an error when security requirements are specified without components security schemes', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
},
|
||||
}
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`Prototype document must contain ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} when security requirements are specified`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error when components security schemes are specified without security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`Prototype document must have ${chalk.bold('security')} defined ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} are specified`
|
||||
);
|
||||
});
|
||||
|
||||
it('overrides root level `security`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.security).toEqual([{ ShouldBeUsedSecurityRequirement: [] }]);
|
||||
expect(bundledSpec.components?.securitySchemes).toEqual({
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('drops operation level security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.security).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.security).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
it('bundles root level security requirements', async () => {
|
||||
const spec1Security = [{ SomeSecurityRequirement: [] }];
|
||||
const spec1SecuritySchemes = {
|
||||
SomeSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
} as const;
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: spec1Security,
|
||||
components: {
|
||||
securitySchemes: spec1SecuritySchemes,
|
||||
},
|
||||
});
|
||||
const spec2Security: OpenAPIV3.SecurityRequirementObject[] = [
|
||||
{ AnotherSecurityRequirement: [] },
|
||||
{ AdditionalSecurityRequirement: [] },
|
||||
];
|
||||
const spec2SecuritySchemes = {
|
||||
AnotherSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
AdditionalSecurityRequirement: {
|
||||
type: 'apiKey',
|
||||
name: 'apiKey',
|
||||
in: 'header',
|
||||
},
|
||||
} as const;
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: spec2Security,
|
||||
components: {
|
||||
securitySchemes: spec2SecuritySchemes,
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.security).toEqual(
|
||||
expect.arrayContaining([...spec1Security, ...spec2Security])
|
||||
);
|
||||
expect(bundledSpec.components?.securitySchemes).toMatchObject({
|
||||
...spec1SecuritySchemes,
|
||||
...spec2SecuritySchemes,
|
||||
});
|
||||
});
|
||||
|
||||
it('bundles operation level security requirements', async () => {
|
||||
const spec1Security = [{ SomeSecurityRequirement: [] }];
|
||||
const spec1SecuritySchemes = {
|
||||
SomeSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
} as const;
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: spec1Security,
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: spec1SecuritySchemes,
|
||||
},
|
||||
});
|
||||
const spec2Security: OpenAPIV3.SecurityRequirementObject[] = [
|
||||
{ AnotherSecurityRequirement: [] },
|
||||
{ AdditionalSecurityRequirement: [] },
|
||||
];
|
||||
const spec2SecuritySchemes = {
|
||||
AnotherSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
AdditionalSecurityRequirement: {
|
||||
type: 'apiKey',
|
||||
name: 'apiKey',
|
||||
in: 'header',
|
||||
},
|
||||
} as const;
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: spec2Security,
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: spec2SecuritySchemes,
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.security).toEqual(spec1Security);
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.security).toEqual(spec2Security);
|
||||
expect(bundledSpec.components?.securitySchemes).toMatchObject({
|
||||
...spec1SecuritySchemes,
|
||||
...spec2SecuritySchemes,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createOASDocument } from '../../create_oas_document';
|
||||
import { bundleSpecs } from '../bundle_specs';
|
||||
|
||||
describe('OpenAPI Bundler - with `servers` overrides', () => {
|
||||
describe('enabled', () => {
|
||||
it('overrides root level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.servers).toEqual([
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('drops path level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.servers).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.servers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('drops operation level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.servers).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.servers).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
it('bundles root level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
const DEFAULT_ENTRY = {
|
||||
url: 'http://{kibana_host}:{port}',
|
||||
variables: {
|
||||
kibana_host: {
|
||||
default: 'localhost',
|
||||
},
|
||||
port: {
|
||||
default: '5601',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(bundledSpec.servers).toEqual([DEFAULT_ENTRY, ...spec1Servers, ...spec2Servers]);
|
||||
});
|
||||
|
||||
it('bundles path level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.servers).toEqual(spec1Servers);
|
||||
expect(bundledSpec.paths['/api/another_api']?.servers).toEqual(spec2Servers);
|
||||
});
|
||||
|
||||
it('bundles operation level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await bundleSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.servers).toEqual(spec1Servers);
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.servers).toEqual(spec2Servers);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { createOASDocument } from '../../create_oas_document';
|
||||
import { mergeSpecs } from '../merge_specs';
|
||||
|
||||
// Disable naming convention check due to tests on spec title prefixes
|
||||
// like Spec1_Something which violates that rule
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
describe('OpenAPI Merger - with security requirements overrides', () => {
|
||||
describe('enabled', () => {
|
||||
it('throws an error when security requirements are specified without components security schemes', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
},
|
||||
}
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`Prototype document must contain ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} when security requirements are specified`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws an error when components security schemes are specified without security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
).rejects.toThrowError(
|
||||
`Prototype document must have ${chalk.bold('security')} defined ${chalk.bold(
|
||||
'components.securitySchemes'
|
||||
)} are specified`
|
||||
);
|
||||
});
|
||||
|
||||
it('overrides root level `security`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.security).toEqual([{ ShouldBeUsedSecurityRequirement: [] }]);
|
||||
expect(bundledSpec.components?.securitySchemes).toEqual({
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('drops operation level security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
security: [{ ShouldBeUsedSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
ShouldBeUsedSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.security).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.security).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
it('bundles root level security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
info: {
|
||||
title: 'Spec1',
|
||||
},
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
SomeSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
info: {
|
||||
title: 'Spec2',
|
||||
},
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
AnotherSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
AdditionalSecurityRequirement: {
|
||||
type: 'apiKey',
|
||||
name: 'apiKey',
|
||||
in: 'header',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.security).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ Spec1_SomeSecurityRequirement: [] },
|
||||
{ Spec2_AnotherSecurityRequirement: [] },
|
||||
{ Spec2_AdditionalSecurityRequirement: [] },
|
||||
])
|
||||
);
|
||||
expect(bundledSpec.components?.securitySchemes).toMatchObject({
|
||||
Spec1_SomeSecurityRequirement: expect.anything(),
|
||||
Spec2_AnotherSecurityRequirement: expect.anything(),
|
||||
Spec2_AdditionalSecurityRequirement: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
it('bundles operation level security requirements', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
info: {
|
||||
title: 'Spec1',
|
||||
},
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ SomeSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
SomeSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
info: {
|
||||
title: 'Spec2',
|
||||
},
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
security: [{ AnotherSecurityRequirement: [] }, { AdditionalSecurityRequirement: [] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
AnotherSecurityRequirement: {
|
||||
type: 'http',
|
||||
scheme: 'basic',
|
||||
},
|
||||
AdditionalSecurityRequirement: {
|
||||
type: 'apiKey',
|
||||
name: 'apiKey',
|
||||
in: 'header',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.security).toEqual([
|
||||
{ Spec1_SomeSecurityRequirement: [] },
|
||||
]);
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.security).toEqual([
|
||||
{ Spec2_AnotherSecurityRequirement: [] },
|
||||
{ Spec2_AdditionalSecurityRequirement: [] },
|
||||
]);
|
||||
expect(bundledSpec.components?.securitySchemes).toMatchObject({
|
||||
Spec1_SomeSecurityRequirement: expect.anything(),
|
||||
Spec2_AnotherSecurityRequirement: expect.anything(),
|
||||
Spec2_AdditionalSecurityRequirement: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createOASDocument } from '../../create_oas_document';
|
||||
import { mergeSpecs } from '../merge_specs';
|
||||
|
||||
describe('OpenAPI Merger - with `servers` overrides', () => {
|
||||
describe('enabled', () => {
|
||||
it('overrides root level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.servers).toEqual([
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('drops path level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.servers).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.servers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('drops operation level `servers`', async () => {
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [{ url: 'https://some-url' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs(
|
||||
{
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
},
|
||||
{
|
||||
prototypeDocument: {
|
||||
servers: [
|
||||
{ url: 'https://should-be-used-url', description: 'Should be used description' },
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.servers).toBeUndefined();
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.servers).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
it('bundles root level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
const DEFAULT_ENTRY = {
|
||||
url: 'http://{kibana_host}:{port}',
|
||||
variables: {
|
||||
kibana_host: {
|
||||
default: 'localhost',
|
||||
},
|
||||
port: {
|
||||
default: '5601',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(bundledSpec.servers).toEqual([DEFAULT_ENTRY, ...spec1Servers, ...spec2Servers]);
|
||||
});
|
||||
|
||||
it('bundles path level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.servers).toEqual(spec1Servers);
|
||||
expect(bundledSpec.paths['/api/another_api']?.servers).toEqual(spec2Servers);
|
||||
});
|
||||
|
||||
it('bundles operation level `servers`', async () => {
|
||||
const spec1Servers = [{ url: 'https://some-url' }];
|
||||
const spec1 = createOASDocument({
|
||||
paths: {
|
||||
'/api/some_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec1Servers,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const spec2Servers = [
|
||||
{ url: 'https://another-url', description: 'some description' },
|
||||
{ url: 'https://something-else-url', description: 'some description' },
|
||||
];
|
||||
const spec2 = createOASDocument({
|
||||
paths: {
|
||||
'/api/another_api': {
|
||||
get: {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
servers: spec2Servers,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [bundledSpec] = Object.values(
|
||||
await mergeSpecs({
|
||||
1: spec1,
|
||||
2: spec2,
|
||||
})
|
||||
);
|
||||
|
||||
expect(bundledSpec.paths['/api/some_api']?.get?.servers).toEqual(spec1Servers);
|
||||
expect(bundledSpec.paths['/api/another_api']?.get?.servers).toEqual(spec2Servers);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -21,10 +21,12 @@ const ROOT = resolve(__dirname, '..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Exceptions API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
"Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.",
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Exceptions API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
"Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -37,10 +39,12 @@ const ROOT = resolve(__dirname, '..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Exceptions API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
"Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.",
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Exceptions API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
"Exceptions API allows you to manage detection rule exceptions to prevent a rule from generating an alert from incoming events even when the rule's other criteria are met.",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -21,9 +21,11 @@ const ROOT = resolve(__dirname, '..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Lists API (Elastic Cloud Serverless)',
|
||||
description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Lists API (Elastic Cloud Serverless)',
|
||||
description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -36,9 +38,11 @@ const ROOT = resolve(__dirname, '..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Lists API (Elastic Cloud and self-hosted)',
|
||||
description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Lists API (Elastic Cloud and self-hosted)',
|
||||
description: 'Lists API allows you to manage lists of keywords, IPs or IP ranges items.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -21,9 +21,11 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security AI Assistant API (Elastic Cloud Serverless)',
|
||||
description: 'Manage and interact with Security Assistant resources.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security AI Assistant API (Elastic Cloud Serverless)',
|
||||
description: 'Manage and interact with Security Assistant resources.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -36,9 +38,11 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security AI Assistant API (Elastic Cloud & self-hosted)',
|
||||
description: 'Manage and interact with Security Assistant resources.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security AI Assistant API (Elastic Cloud & self-hosted)',
|
||||
description: 'Manage and interact with Security Assistant resources.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,9 +20,11 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..');
|
|||
outputFilePath: 'docs/openapi/serverless/osquery_api_{version}.bundled.schema.yaml',
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Osquery API (Elastic Cloud Serverless)',
|
||||
description: 'Run live queries, manage packs and saved queries.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Osquery API (Elastic Cloud Serverless)',
|
||||
description: 'Run live queries, manage packs and saved queries.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -33,9 +35,11 @@ const ELASTIC_ASSISTANT_ROOT = resolve(__dirname, '../..');
|
|||
outputFilePath: 'docs/openapi/ess/osquery_api_{version}.bundled.schema.yaml',
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Osquery API (Elastic Cloud and self-hosted)',
|
||||
description: 'Run live queries, manage packs and saved queries.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Osquery API (Elastic Cloud and self-hosted)',
|
||||
description: 'Run live queries, manage packs and saved queries.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,10 +20,12 @@ const ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud Serverless)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -36,10 +38,12 @@ const ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Detections API (Elastic Cloud and self-hosted)',
|
||||
description:
|
||||
'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -20,9 +20,11 @@ const ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Endpoint Management API (Elastic Cloud Serverless)',
|
||||
description: 'Interact with and manage endpoints running the Elastic Defend integration.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Endpoint Management API (Elastic Cloud Serverless)',
|
||||
description: 'Interact with and manage endpoints running the Elastic Defend integration.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -35,9 +37,11 @@ const ROOT = resolve(__dirname, '../..');
|
|||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Endpoint Management API (Elastic Cloud and self-hosted)',
|
||||
description: 'Interact with and manage endpoints running the Elastic Defend integration.',
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Endpoint Management API (Elastic Cloud and self-hosted)',
|
||||
description: 'Interact with and manage endpoints running the Elastic Defend integration.',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -11,32 +11,38 @@ const { join, resolve } = require('path');
|
|||
|
||||
const ROOT = resolve(__dirname, '../..');
|
||||
|
||||
bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/serverless/security_solution_entity_analytics_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Entity Analytics API (Elastic Cloud Serverless)',
|
||||
description: '',
|
||||
(async () => {
|
||||
await bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/serverless/security_solution_entity_analytics_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['serverless'],
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Entity Analytics API (Elastic Cloud Serverless)',
|
||||
description: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/ess/security_solution_entity_analytics_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
specInfo: {
|
||||
title: 'Security Solution Entity Analytics API (Elastic Cloud and self-hosted)',
|
||||
description: '',
|
||||
await bundle({
|
||||
sourceGlob: join(ROOT, 'common/api/entity_analytics/**/*.schema.yaml'),
|
||||
outputFilePath: join(
|
||||
ROOT,
|
||||
'docs/openapi/ess/security_solution_entity_analytics_api_{version}.bundled.schema.yaml'
|
||||
),
|
||||
options: {
|
||||
includeLabels: ['ess'],
|
||||
prototypeDocument: {
|
||||
info: {
|
||||
title: 'Security Solution Entity Analytics API (Elastic Cloud and self-hosted)',
|
||||
description: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue