mirror of
https://github.com/LukeHagar/prettier-plugin-openapi.git
synced 2025-12-06 12:47:47 +00:00
Refactor OpenAPI plugin structure by consolidating parser and printer exports, enhancing type safety, and improving error handling in parsing functions. Update tests to reflect new structure and ensure consistent handling of OpenAPI file detection.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import {
|
||||
RootKeys,
|
||||
InfoKeys,
|
||||
ComponentsKeys,
|
||||
OperationKeys,
|
||||
ParameterKeys,
|
||||
SchemaKeys,
|
||||
@@ -18,13 +19,21 @@ import {
|
||||
TagKeys,
|
||||
ExternalDocsKeys,
|
||||
WebhookKeys,
|
||||
OAuthFlowKeys,
|
||||
PathItemKeys,
|
||||
RequestBodyKeys,
|
||||
MediaTypeKeys,
|
||||
EncodingKeys,
|
||||
HeaderKeys,
|
||||
LinkKeys,
|
||||
ExampleKeys,
|
||||
DiscriminatorKeys,
|
||||
XMLKeys,
|
||||
ContactKeys,
|
||||
LicenseKeys,
|
||||
ComponentsKeys,
|
||||
OAuthFlowKeys,
|
||||
ServerVariableKeys,
|
||||
} from '../keys.js';
|
||||
import { getVendorExtensions, type VendorModule } from './vendor-loader.js';
|
||||
import { type VendorModule } from './vendor-loader.js';
|
||||
|
||||
/**
|
||||
* Type-safe context-specific extension functions
|
||||
@@ -528,6 +537,307 @@ export interface VendorExtensions {
|
||||
* ```
|
||||
*/
|
||||
'securityDefinitions'?: ContextExtensionFunction<'securityDefinitions'>;
|
||||
/** Components section extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `securitySchemes`
|
||||
* - `pathItems`
|
||||
* - `parameters`
|
||||
* - `headers`
|
||||
* - `requestBodies`
|
||||
* - `responses`
|
||||
* - `callbacks`
|
||||
* - `links`
|
||||
* - `schemas`
|
||||
* - `examples`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'components': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('schemas'),
|
||||
* 'x-my-config': after('examples')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'components'?: ContextExtensionFunction<'components'>;
|
||||
/** Path item extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `$ref`
|
||||
* - `summary`
|
||||
* - `description`
|
||||
* - `servers`
|
||||
* - `parameters`
|
||||
* - `get`
|
||||
* - `put`
|
||||
* - `post`
|
||||
* - `patch`
|
||||
* - `delete`
|
||||
* - `options`
|
||||
* - `head`
|
||||
* - `trace`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'pathItem': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('summary'),
|
||||
* 'x-my-config': after('parameters')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'pathItem'?: ContextExtensionFunction<'pathItem'>;
|
||||
/** Request body extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `description`
|
||||
* - `required`
|
||||
* - `content`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'requestBody': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('description'),
|
||||
* 'x-my-config': after('content')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'requestBody'?: ContextExtensionFunction<'requestBody'>;
|
||||
/** Media type extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `schema`
|
||||
* - `example`
|
||||
* - `examples`
|
||||
* - `encoding`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'mediaType': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('schema'),
|
||||
* 'x-my-config': after('example')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'mediaType'?: ContextExtensionFunction<'mediaType'>;
|
||||
/** Encoding extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `contentType`
|
||||
* - `style`
|
||||
* - `explode`
|
||||
* - `allowReserved`
|
||||
* - `headers`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'encoding': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('contentType'),
|
||||
* 'x-my-config': after('headers')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'encoding'?: ContextExtensionFunction<'encoding'>;
|
||||
/** Header extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `description`
|
||||
* - `required`
|
||||
* - `deprecated`
|
||||
* - `schema`
|
||||
* - `content`
|
||||
* - `type`
|
||||
* - `format`
|
||||
* - `style`
|
||||
* - `explode`
|
||||
* - `enum`
|
||||
* - `default`
|
||||
* - `example`
|
||||
* - `examples`
|
||||
* - `items`
|
||||
* - `collectionFormat`
|
||||
* - `maxItems`
|
||||
* - `minItems`
|
||||
* - `uniqueItems`
|
||||
* - `minimum`
|
||||
* - `multipleOf`
|
||||
* - `exclusiveMinimum`
|
||||
* - `maximum`
|
||||
* - `exclusiveMaximum`
|
||||
* - `pattern`
|
||||
* - `minLength`
|
||||
* - `maxLength`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'header': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('description'),
|
||||
* 'x-my-config': after('schema')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'header'?: ContextExtensionFunction<'header'>;
|
||||
/** Link extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `operationId`
|
||||
* - `description`
|
||||
* - `server`
|
||||
* - `operationRef`
|
||||
* - `parameters`
|
||||
* - `requestBody`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'link': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('operationId'),
|
||||
* 'x-my-config': after('parameters')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'link'?: ContextExtensionFunction<'link'>;
|
||||
/** Example extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `summary`
|
||||
* - `description`
|
||||
* - `value`
|
||||
* - `externalValue`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'example': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('summary'),
|
||||
* 'x-my-config': after('value')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'example'?: ContextExtensionFunction<'example'>;
|
||||
/** Discriminator extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `propertyName`
|
||||
* - `mapping`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'discriminator': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('propertyName'),
|
||||
* 'x-my-config': after('mapping')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'discriminator'?: ContextExtensionFunction<'discriminator'>;
|
||||
/** XML extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `name`
|
||||
* - `namespace`
|
||||
* - `prefix`
|
||||
* - `attribute`
|
||||
* - `wrapped`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'xml': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('name'),
|
||||
* 'x-my-config': after('namespace')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'xml'?: ContextExtensionFunction<'xml'>;
|
||||
/** Contact extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `name`
|
||||
* - `email`
|
||||
* - `url`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'contact': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('name'),
|
||||
* 'x-my-config': after('email')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'contact'?: ContextExtensionFunction<'contact'>;
|
||||
/** License extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `name`
|
||||
* - `identifier`
|
||||
* - `url`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'license': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('name'),
|
||||
* 'x-my-config': after('identifier')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'license'?: ContextExtensionFunction<'license'>;
|
||||
/** OAuth flow extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `authorizationUrl`
|
||||
* - `tokenUrl`
|
||||
* - `refreshUrl`
|
||||
* - `scopes`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'oauthFlow': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('authorizationUrl'),
|
||||
* 'x-my-config': after('tokenUrl')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'oauthFlow'?: ContextExtensionFunction<'oauthFlow'>;
|
||||
/** Server variable extensions
|
||||
*
|
||||
* Available keys:
|
||||
* - `description`
|
||||
* - `default`
|
||||
* - `enum`
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* 'serverVariable': (before, after) => {
|
||||
* return {
|
||||
* 'x-my-extension': before('description'),
|
||||
* 'x-my-config': after('default')
|
||||
* };
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
'serverVariable'?: ContextExtensionFunction<'serverVariable'>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,6 +919,7 @@ export function createPositionHelpers<T extends keyof typeof KeyMap>(context: T)
|
||||
export const KeyMap = {
|
||||
'top-level': RootKeys,
|
||||
'info': InfoKeys,
|
||||
'components': ComponentsKeys,
|
||||
'operation': OperationKeys,
|
||||
'parameter': ParameterKeys,
|
||||
'schema': SchemaKeys,
|
||||
@@ -618,6 +929,19 @@ export const KeyMap = {
|
||||
'tag': TagKeys,
|
||||
'externalDocs': ExternalDocsKeys,
|
||||
'webhook': WebhookKeys,
|
||||
'pathItem': PathItemKeys,
|
||||
'requestBody': RequestBodyKeys,
|
||||
'mediaType': MediaTypeKeys,
|
||||
'encoding': EncodingKeys,
|
||||
'header': HeaderKeys,
|
||||
'link': LinkKeys,
|
||||
'example': ExampleKeys,
|
||||
'discriminator': DiscriminatorKeys,
|
||||
'xml': XMLKeys,
|
||||
'contact': ContactKeys,
|
||||
'license': LicenseKeys,
|
||||
'oauthFlow': OAuthFlowKeys,
|
||||
'serverVariable': ServerVariableKeys,
|
||||
'definitions': SchemaKeys,
|
||||
'securityDefinitions': SecuritySchemeKeys,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Loads vendor extensions using static imports for ES module compatibility.
|
||||
*/
|
||||
|
||||
import { before, after, KeyMap, VendorExtensions } from './index.js';
|
||||
import { before, after, type KeyMap, type VendorExtensions } from './index.js';
|
||||
|
||||
// Import vendor extensions statically
|
||||
import { speakeasy } from './vendor/speakeasy.js';
|
||||
|
||||
285
src/index.ts
285
src/index.ts
@@ -1,48 +1,45 @@
|
||||
import { SupportLanguage, Parser, Printer } from 'prettier';
|
||||
import type { Plugin } from 'prettier';
|
||||
import * as yaml from 'js-yaml';
|
||||
import type{ AstPath, Doc, Parser, ParserOptions, Printer, SupportLanguage } from 'prettier';
|
||||
import { getVendorExtensions } from './extensions/vendor-loader.js';
|
||||
|
||||
export type PrintFn = (path: AstPath) => Doc;
|
||||
|
||||
import {
|
||||
RootKeys,
|
||||
InfoKeys,
|
||||
ContactKeys,
|
||||
LicenseKeys,
|
||||
ComponentsKeys,
|
||||
OperationKeys,
|
||||
ParameterKeys,
|
||||
SchemaKeys,
|
||||
ResponseKeys,
|
||||
SecuritySchemeKeys,
|
||||
OAuthFlowKeys,
|
||||
ServerKeys,
|
||||
ServerVariableKeys,
|
||||
TagKeys,
|
||||
ExternalDocsKeys,
|
||||
WebhookKeys,
|
||||
PathItemKeys,
|
||||
RequestBodyKeys,
|
||||
MediaTypeKeys,
|
||||
EncodingKeys,
|
||||
HeaderKeys,
|
||||
LinkKeys,
|
||||
ExampleKeys,
|
||||
DiscriminatorKeys,
|
||||
XMLKeys,
|
||||
ComponentsKeys,
|
||||
ContactKeys,
|
||||
DiscriminatorKeys,
|
||||
EncodingKeys,
|
||||
ExampleKeys,
|
||||
ExternalDocsKeys,
|
||||
HeaderKeys,
|
||||
InfoKeys,
|
||||
LicenseKeys,
|
||||
LinkKeys,
|
||||
MediaTypeKeys,
|
||||
OAuthFlowKeys,
|
||||
OperationKeys,
|
||||
ParameterKeys,
|
||||
PathItemKeys,
|
||||
RequestBodyKeys,
|
||||
ResponseKeys,
|
||||
RootKeys,
|
||||
SchemaKeys,
|
||||
SecuritySchemeKeys,
|
||||
ServerKeys,
|
||||
ServerVariableKeys,
|
||||
TagKeys,
|
||||
WebhookKeys,
|
||||
XMLKeys,
|
||||
} from './keys.js';
|
||||
|
||||
// Type definitions for better type safety
|
||||
interface OpenAPINode {
|
||||
type: 'openapi';
|
||||
isOpenAPI: boolean;
|
||||
content: any;
|
||||
originalText: string;
|
||||
format: 'json' | 'yaml';
|
||||
}
|
||||
|
||||
interface PrettierPath {
|
||||
getValue(): OpenAPINode;
|
||||
}
|
||||
|
||||
interface OpenAPIPluginOptions {
|
||||
tabWidth?: number;
|
||||
printWidth?: number;
|
||||
@@ -59,18 +56,22 @@ const vendorExtensions = getVendorExtensions();
|
||||
* Unified parser that can handle both JSON and YAML OpenAPI files
|
||||
*/
|
||||
function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
|
||||
// Try to detect the format based on file extension or content
|
||||
const filePath = options?.filepath || '';
|
||||
const isYamlFile = filePath.endsWith('.yaml') || filePath.endsWith('.yml');
|
||||
const isJsonFile = filePath.endsWith('.json');
|
||||
|
||||
// If we can't determine from extension, try to detect from content
|
||||
let format: 'json' | 'yaml' = 'json'; // default to JSON
|
||||
if (isYamlFile) {
|
||||
format = 'yaml';
|
||||
} else if (isJsonFile) {
|
||||
format = 'json';
|
||||
} else {
|
||||
console.debug('parseOpenAPIFile', text, options);
|
||||
|
||||
let format: 'json' | 'yaml' | undefined;
|
||||
|
||||
if (options?.filepath) {
|
||||
switch (true) {
|
||||
case options?.filepath.endsWith('.yaml') || options?.filepath.endsWith('.yml'):
|
||||
format = 'yaml';
|
||||
break;
|
||||
case options?.filepath.endsWith('.json'):
|
||||
format = 'json';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
// Try to detect format from content
|
||||
const trimmedText = text.trim();
|
||||
if (trimmedText.startsWith('{') || trimmedText.startsWith('[')) {
|
||||
@@ -79,35 +80,61 @@ function parseOpenAPIFile(text: string, options?: any): OpenAPINode {
|
||||
format = 'yaml';
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let parsed: any;
|
||||
|
||||
if (format === 'json') {
|
||||
parsed = JSON.parse(text);
|
||||
} else {
|
||||
|
||||
let parsed: any;
|
||||
|
||||
switch (format) {
|
||||
case 'yaml':
|
||||
try {
|
||||
parsed = yaml.load(text, {
|
||||
schema: yaml.DEFAULT_SCHEMA,
|
||||
onWarning: (warning) => {
|
||||
// Handle YAML warnings if needed
|
||||
console.warn('YAML parsing warning:', warning);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check if this is an OpenAPI file
|
||||
if (!isOpenAPIFile(parsed, filePath)) {
|
||||
throw new Error('Not an OpenAPI file');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse OpenAPI YAML: ${error}`);
|
||||
}
|
||||
break;
|
||||
case 'json':
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse OpenAPI JSON: ${error}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
let isOpenAPI: boolean;
|
||||
|
||||
try {
|
||||
isOpenAPI = isOpenAPIFile(parsed, options?.filepath);
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'openapi',
|
||||
isOpenAPI: false,
|
||||
content: parsed,
|
||||
originalText: text,
|
||||
format: format,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse OpenAPI ${format.toUpperCase()}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (isOpenAPI) {
|
||||
case true:
|
||||
return {
|
||||
isOpenAPI: true,
|
||||
content: parsed,
|
||||
originalText: text,
|
||||
format: format,
|
||||
}
|
||||
case false:
|
||||
default:
|
||||
return {
|
||||
isOpenAPI: false,
|
||||
content: parsed,
|
||||
originalText: text,
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,17 +159,17 @@ function isOpenAPIFile(content: any, filePath?: string): boolean {
|
||||
// Only accept files in OpenAPI-related directories
|
||||
if (filePath) {
|
||||
const path = filePath.toLowerCase();
|
||||
|
||||
|
||||
// Check for component directory patterns
|
||||
if (path.includes('/components/') ||
|
||||
path.includes('/schemas/') ||
|
||||
path.includes('/parameters/') ||
|
||||
path.includes('/responses/') ||
|
||||
path.includes('/requestbodies/') ||
|
||||
path.includes('/headers/') ||
|
||||
path.includes('/examples/') ||
|
||||
path.includes('/securityschemes/') ||
|
||||
path.includes('/links/') ||
|
||||
if (path.includes('/components/') ||
|
||||
path.includes('/schemas/') ||
|
||||
path.includes('/parameters/') ||
|
||||
path.includes('/responses/') ||
|
||||
path.includes('/requestbodies/') ||
|
||||
path.includes('/headers/') ||
|
||||
path.includes('/examples/') ||
|
||||
path.includes('/securityschemes/') ||
|
||||
path.includes('/links/') ||
|
||||
path.includes('/callbacks/') ||
|
||||
path.includes('/webhooks/') ||
|
||||
path.includes('/paths/')) {
|
||||
@@ -204,11 +231,11 @@ function isOpenAPIFile(content: any, filePath?: string): boolean {
|
||||
// Additional strict check: reject objects that look like generic data
|
||||
// If an object only has simple properties like name, age, etc. without any OpenAPI structure, reject it
|
||||
const keys = Object.keys(content);
|
||||
const hasOnlyGenericProperties = keys.every(key =>
|
||||
const hasOnlyGenericProperties = keys.every(key =>
|
||||
!key.startsWith('x-') && // Not a custom extension
|
||||
!['openapi', 'swagger', 'info', 'paths', 'components', 'definitions', 'parameters', 'responses', 'securityDefinitions', 'tags', 'servers', 'webhooks'].includes(key)
|
||||
);
|
||||
|
||||
|
||||
if (hasOnlyGenericProperties) {
|
||||
return false;
|
||||
}
|
||||
@@ -229,36 +256,40 @@ function isPathObject(obj: any): boolean {
|
||||
return Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase()));
|
||||
}
|
||||
|
||||
const plugin: Plugin = {
|
||||
languages: [
|
||||
{
|
||||
name: 'openapi',
|
||||
extensions: [
|
||||
// Accepting all JSON and YAML files so that component files used by $ref work
|
||||
'.json', '.yaml', '.yml'
|
||||
],
|
||||
parsers: ['openapi-parser'],
|
||||
export const languages: Partial<SupportLanguage>[] = [
|
||||
{
|
||||
name: 'openapi',
|
||||
extensions: [
|
||||
// Accepting all JSON and YAML files so that component files used by $ref work
|
||||
'.json', '.yaml', '.yml'
|
||||
],
|
||||
parsers: ['openapi-parser'],
|
||||
},
|
||||
];
|
||||
|
||||
export const parsers: Record<string, Parser> = {
|
||||
'openapi-parser': {
|
||||
parse: (text: string, options?: any): OpenAPINode => {
|
||||
return parseOpenAPIFile(text, options);
|
||||
},
|
||||
],
|
||||
parsers: {
|
||||
'openapi-parser': {
|
||||
parse: (text: string, options?: any): OpenAPINode => {
|
||||
return parseOpenAPIFile(text, options);
|
||||
},
|
||||
astFormat: 'openapi-ast',
|
||||
locStart: (node: OpenAPINode) => 0,
|
||||
locEnd: (node: OpenAPINode) => node.originalText?.length || 0,
|
||||
astFormat: 'openapi-ast',
|
||||
locStart: (node: OpenAPINode) => 0,
|
||||
locEnd: (node: OpenAPINode) => node.originalText?.length || 0,
|
||||
},
|
||||
}
|
||||
|
||||
export const printers: Record<string, Printer> = {
|
||||
'openapi-ast': {
|
||||
print: (path: AstPath, options: ParserOptions, print: PrintFn): string => {
|
||||
const node = path.getNode();
|
||||
if (!node.isOpenAPI || node.isOpenAPI === false) {
|
||||
// Return original text unchanged
|
||||
return options.originalText;
|
||||
}
|
||||
return formatOpenAPI(node.content, node.format, options);
|
||||
},
|
||||
},
|
||||
printers: {
|
||||
'openapi-ast': {
|
||||
print: (path: PrettierPath, options?: any, print?: any, ...rest: any[]): string => {
|
||||
const node = path.getValue();
|
||||
return formatOpenAPI(node.content, node.format, options);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified formatter that outputs in the detected format
|
||||
@@ -267,29 +298,21 @@ function formatOpenAPI(content: any, format: 'json' | 'yaml', options?: OpenAPIP
|
||||
// Sort keys for better organization
|
||||
const sortedContent = sortOpenAPIKeys(content);
|
||||
|
||||
if (format === 'json') {
|
||||
// Format with proper indentation
|
||||
return JSON.stringify(sortedContent, null, options?.tabWidth || 2);
|
||||
} else {
|
||||
// Format YAML with proper indentation and line breaks
|
||||
return yaml.dump(sortedContent, {
|
||||
indent: options?.tabWidth || 2,
|
||||
lineWidth: options?.printWidth || 80,
|
||||
noRefs: true,
|
||||
quotingType: '"',
|
||||
forceQuotes: false,
|
||||
});
|
||||
switch (format) {
|
||||
case 'json':
|
||||
return JSON.stringify(sortedContent, null, options?.tabWidth || 2);
|
||||
case 'yaml':
|
||||
// Format YAML with proper indentation and line breaks
|
||||
return yaml.dump(sortedContent, {
|
||||
indent: options?.tabWidth || 2,
|
||||
lineWidth: options?.printWidth || 80,
|
||||
noRefs: true,
|
||||
quotingType: '"',
|
||||
forceQuotes: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function formatOpenAPIJSON(content: any, options?: OpenAPIPluginOptions): string {
|
||||
return formatOpenAPI(content, 'json', options);
|
||||
}
|
||||
|
||||
function formatOpenAPIYAML(content: any, options?: OpenAPIPluginOptions): string {
|
||||
return formatOpenAPI(content, 'yaml', options);
|
||||
}
|
||||
|
||||
function sortOpenAPIKeys(obj: any): any {
|
||||
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
||||
return obj;
|
||||
@@ -316,7 +339,7 @@ function sortOpenAPIKeysEnhanced(obj: any, path: string = ''): any {
|
||||
if (typeof obj !== 'object' || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
// Handle arrays by recursively sorting each element
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item, index) => sortOpenAPIKeysEnhanced(item, `${path}[${index}]`));
|
||||
@@ -389,11 +412,11 @@ function isSchemaObject(obj: any): boolean {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check for JSON Schema keywords - be very strict
|
||||
const hasSchemaKeywords = '$ref' in obj || 'allOf' in obj || 'oneOf' in obj || 'anyOf' in obj || 'not' in obj;
|
||||
const hasValidType = 'type' in obj && obj.type && ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(obj.type);
|
||||
|
||||
|
||||
// Only return true if we have clear schema indicators
|
||||
// Must have either schema keywords OR valid type with schema properties
|
||||
// Also require additional schema-specific properties to be more strict
|
||||
@@ -414,8 +437,8 @@ function isServerObject(obj: any): boolean {
|
||||
}
|
||||
|
||||
function isTagObject(obj: any): boolean {
|
||||
return obj && typeof obj === 'object' && 'name' in obj && typeof obj.name === 'string' &&
|
||||
(Object.keys(obj).length === 1 || // Only name
|
||||
return obj && typeof obj === 'object' && 'name' in obj && typeof obj.name === 'string' &&
|
||||
(Object.keys(obj).length === 1 || // Only name
|
||||
'description' in obj || // name + description
|
||||
'externalDocs' in obj); // name + externalDocs
|
||||
}
|
||||
@@ -546,7 +569,7 @@ function getContextKey(path: string, obj: any): string {
|
||||
if (path === 'webhooks') return 'webhook';
|
||||
if (path === 'definitions') return 'definitions';
|
||||
if (path === 'securityDefinitions') return 'securityDefinitions';
|
||||
|
||||
|
||||
// Check if this is a path operation (e.g., "paths./users.get")
|
||||
if (path.includes('.') && path.split('.').length >= 3) {
|
||||
const pathParts = path.split('.');
|
||||
@@ -648,6 +671,4 @@ function getStandardKeysForContext(contextKey: string): readonly string[] {
|
||||
case 'securityDefinitions': return SecuritySchemeKeys; // Security definitions use security scheme keys
|
||||
default: return RootKeys;
|
||||
}
|
||||
}
|
||||
|
||||
export default plugin;
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
describe('Build Tests', () => {
|
||||
describe('Build artifacts', () => {
|
||||
it('should create dist directory', () => {
|
||||
const distPath = path.join(process.cwd(), 'dist');
|
||||
expect(fs.existsSync(distPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should create main index.js file', () => {
|
||||
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
|
||||
expect(fs.existsSync(indexPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should create TypeScript declaration files', () => {
|
||||
const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts');
|
||||
expect(fs.existsSync(dtsPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should create source map files', () => {
|
||||
const mapPath = path.join(process.cwd(), 'dist', 'index.js.map');
|
||||
expect(fs.existsSync(mapPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should have valid JavaScript in dist/index.js', () => {
|
||||
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
|
||||
// Should not contain TypeScript syntax
|
||||
expect(content).not.toContain(': string');
|
||||
expect(content).not.toContain(': number');
|
||||
expect(content).not.toContain('interface ');
|
||||
// Note: 'type ' might appear in comments or strings, so we check for type annotations instead
|
||||
expect(content).not.toMatch(/\btype\s+[A-Z]/);
|
||||
|
||||
// Should be valid JavaScript (but may contain ES module syntax)
|
||||
// We can't use new Function() with ES modules, so we just check it's not empty
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should export the plugin as default export', () => {
|
||||
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
|
||||
// Should have module.exports (CommonJS format)
|
||||
expect(content).toContain('module.exports');
|
||||
});
|
||||
|
||||
it('should have proper module structure', () => {
|
||||
const indexPath = path.join(process.cwd(), 'dist', 'index.js');
|
||||
const content = fs.readFileSync(indexPath, 'utf-8');
|
||||
|
||||
// Should be CommonJS module
|
||||
expect(content).toContain('require');
|
||||
expect(content).toContain('module.exports');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Package.json validation', () => {
|
||||
it('should have correct main field', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.main).toBe('dist/index.js');
|
||||
});
|
||||
|
||||
it('should have correct types field', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.types).toBe('./dist/index.d.ts');
|
||||
});
|
||||
|
||||
it('should have correct exports field', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.exports).toBeDefined();
|
||||
expect(packageJson.exports['.']).toBeDefined();
|
||||
expect(packageJson.exports['.'].default).toBe('./dist/index.js');
|
||||
});
|
||||
|
||||
it('should include required files in files array', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.files).toContain('dist/index.js');
|
||||
expect(packageJson.files).toContain('dist/index.d.ts');
|
||||
});
|
||||
|
||||
it('should have required metadata', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
|
||||
expect(packageJson.name).toBeDefined();
|
||||
expect(packageJson.version).toBeDefined();
|
||||
expect(packageJson.description).toBeDefined();
|
||||
expect(packageJson.author).toBeDefined();
|
||||
expect(packageJson.license).toBeDefined();
|
||||
expect(packageJson.keywords).toBeDefined();
|
||||
expect(packageJson.repository).toBeDefined();
|
||||
expect(packageJson.bugs).toBeDefined();
|
||||
expect(packageJson.homepage).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have correct peer dependencies', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.peerDependencies).toBeDefined();
|
||||
expect(packageJson.peerDependencies.prettier).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have correct engines requirement', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
expect(packageJson.engines).toBeDefined();
|
||||
expect(packageJson.engines.node).toBe('>=18.0.0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NPM package validation', () => {
|
||||
it('should have all required files for npm publish', () => {
|
||||
const requiredFiles = [
|
||||
'dist/index.js',
|
||||
'dist/index.d.ts',
|
||||
'dist/index.js.map',
|
||||
'README.md',
|
||||
'package.json'
|
||||
];
|
||||
|
||||
requiredFiles.forEach(file => {
|
||||
const filePath = path.join(process.cwd(), file);
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include development files in npm package', () => {
|
||||
const excludedFiles = [
|
||||
'src/',
|
||||
'test/',
|
||||
'.github/',
|
||||
'.husky/',
|
||||
'.eslintrc.js',
|
||||
'.prettierrc.js',
|
||||
'tsconfig.json',
|
||||
'bunfig.toml'
|
||||
];
|
||||
|
||||
// These files should not be in the npm package
|
||||
// (This is handled by .npmignore, but we can verify the ignore file exists)
|
||||
expect(fs.existsSync('.npmignore')).toBe(true);
|
||||
});
|
||||
|
||||
it('should have valid package.json for npm', () => {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
||||
|
||||
// Required fields for npm
|
||||
expect(packageJson.name).toBeTruthy();
|
||||
expect(packageJson.version).toBeTruthy();
|
||||
expect(packageJson.main).toBeTruthy();
|
||||
expect(packageJson.license).toBeTruthy();
|
||||
|
||||
// Should not have private field (or it should be false)
|
||||
if (packageJson.private !== undefined) {
|
||||
expect(packageJson.private).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('TypeScript compilation', () => {
|
||||
it('should compile without errors', () => {
|
||||
// This test assumes the build has already been run
|
||||
// In a real scenario, you might want to run tsc programmatically
|
||||
const distPath = path.join(process.cwd(), 'dist');
|
||||
expect(fs.existsSync(distPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate declaration files', () => {
|
||||
const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts');
|
||||
const content = fs.readFileSync(dtsPath, 'utf-8');
|
||||
|
||||
// Should contain type declarations
|
||||
expect(content).toContain('declare');
|
||||
expect(content).toContain('export');
|
||||
});
|
||||
|
||||
it('should generate source maps', () => {
|
||||
const mapPath = path.join(process.cwd(), 'dist', 'index.js.map');
|
||||
const content = fs.readFileSync(mapPath, 'utf-8');
|
||||
const sourceMap = JSON.parse(content);
|
||||
|
||||
// Should be valid source map
|
||||
expect(sourceMap.version).toBeDefined();
|
||||
expect(sourceMap.sources).toBeDefined();
|
||||
expect(sourceMap.mappings).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,79 +1,10 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import { parsers, printers } from '../src/index';
|
||||
|
||||
describe('Coverage Tests', () => {
|
||||
describe('Error handling and edge cases', () => {
|
||||
it('should handle null and undefined content', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
// Test with null content
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('null', { filepath: 'test.json' });
|
||||
}).toThrow();
|
||||
|
||||
// Test with undefined content
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('undefined', { filepath: 'test.json' });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should handle non-object content', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
// Test with string content
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('"string"', { filepath: 'test.json' });
|
||||
}).toThrow();
|
||||
|
||||
// Test with number content
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('123', { filepath: 'test.json' });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should handle array content', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
// Test with array content
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('[]', { filepath: 'test.json' });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should handle malformed JSON', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
// Test with malformed JSON
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('{invalid json}', { filepath: 'test.json' });
|
||||
}).toThrow('Failed to parse OpenAPI JSON');
|
||||
});
|
||||
|
||||
it('should handle malformed YAML', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
// Test with malformed YAML
|
||||
expect(() => {
|
||||
// @ts-expect-error We are testing edge cases
|
||||
parser?.parse('invalid: yaml: content:', { filepath: 'test.yaml' });
|
||||
}).toThrow('Failed to parse OpenAPI YAML');
|
||||
});
|
||||
});
|
||||
|
||||
describe('File path detection', () => {
|
||||
it('should detect OpenAPI files in component directories', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testYaml = `type: object
|
||||
@@ -100,13 +31,13 @@ properties:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(testYaml, { filepath: path });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle files without filepath', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testYaml = `openapi: 3.0.0
|
||||
@@ -117,13 +48,13 @@ info:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(testYaml, { filepath: 'test.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Object type detection', () => {
|
||||
it('should detect operation objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const operationYaml = `get:
|
||||
@@ -135,11 +66,11 @@ info:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(operationYaml, { filepath: 'paths/users.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect parameter objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const parameterYaml = `name: id
|
||||
@@ -151,11 +82,11 @@ schema:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect schema objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const schemaYaml = `type: object
|
||||
@@ -170,11 +101,11 @@ required:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect response objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const responseYaml = `description: User response
|
||||
@@ -186,11 +117,11 @@ content:
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect security scheme objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const securityYaml = `type: http
|
||||
@@ -200,11 +131,11 @@ bearerFormat: JWT`;
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect server objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const serverYaml = `url: https://api.example.com
|
||||
@@ -213,11 +144,11 @@ description: Production server`;
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(serverYaml, { filepath: 'servers/production.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect tag objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const tagYaml = `name: users
|
||||
@@ -226,11 +157,11 @@ description: User management operations`;
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(tagYaml, { filepath: 'tags/users.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect external docs objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const externalDocsYaml = `url: https://example.com/docs
|
||||
@@ -239,11 +170,11 @@ description: External documentation`;
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(externalDocsYaml, { filepath: 'externalDocs/api.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
|
||||
it('should detect webhook objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const webhookYaml = `post:
|
||||
@@ -255,17 +186,17 @@ description: External documentation`;
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = parser?.parse(webhookYaml, { filepath: 'webhooks/messageCreated.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sorting functions', () => {
|
||||
it('should handle path sorting by specificity', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -280,9 +211,9 @@ description: External documentation`;
|
||||
};
|
||||
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
|
||||
if (result && typeof result === 'string') {
|
||||
const formatted = JSON.parse(result);
|
||||
const pathKeys = Object.keys(formatted.paths);
|
||||
@@ -294,11 +225,11 @@ description: External documentation`;
|
||||
});
|
||||
|
||||
it('should handle response code sorting', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -319,9 +250,9 @@ description: External documentation`;
|
||||
};
|
||||
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
|
||||
if (result && typeof result === 'string') {
|
||||
const formatted = JSON.parse(result);
|
||||
const responseKeys = Object.keys(formatted.paths['/test'].get.responses);
|
||||
@@ -335,11 +266,11 @@ description: External documentation`;
|
||||
|
||||
describe('Context key detection', () => {
|
||||
it('should handle nested path contexts', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -360,9 +291,9 @@ description: External documentation`;
|
||||
};
|
||||
|
||||
// @ts-expect-error We are testing edge cases
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
|
||||
if (result && typeof result === 'string') {
|
||||
const formatted = JSON.parse(result);
|
||||
expect(formatted.components.schemas.User).toBeDefined();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import {parsers, printers} from '../src/index';
|
||||
|
||||
describe('Custom Extensions Support', () => {
|
||||
it('should handle custom extensions in top-level keys', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testJson = {
|
||||
@@ -21,7 +21,7 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should handle custom extensions in info section', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testJson = {
|
||||
@@ -43,7 +43,7 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should handle custom extensions in operation objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testJson = {
|
||||
@@ -68,7 +68,7 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should handle custom extensions in schema objects', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testJson = {
|
||||
@@ -95,10 +95,12 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should format JSON with custom extensions', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
'x-custom-field': 'value',
|
||||
'openapi': '3.0.0',
|
||||
@@ -109,17 +111,19 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('x-custom-field');
|
||||
expect(result).toContain('openapi');
|
||||
});
|
||||
|
||||
it('should format YAML with custom extensions', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
'x-custom-field': 'value',
|
||||
'openapi': '3.0.0',
|
||||
@@ -130,14 +134,14 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('x-custom-field:');
|
||||
expect(result).toContain('openapi:');
|
||||
});
|
||||
|
||||
it('should handle unknown keys alphabetically at the end', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testJson = {
|
||||
@@ -156,10 +160,12 @@ describe('Custom Extensions Support', () => {
|
||||
|
||||
describe('Custom extension positioning', () => {
|
||||
it('should position custom extensions correctly in top-level', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
'x-custom-field': 'value',
|
||||
'openapi': '3.0.0',
|
||||
@@ -170,7 +176,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -193,10 +199,12 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should position custom extensions correctly in info section', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
'openapi': '3.0.0',
|
||||
'info': {
|
||||
@@ -210,7 +218,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -233,10 +241,12 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should position custom extensions correctly in operation objects', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
'openapi': '3.0.0',
|
||||
'info': { 'title': 'Test API', 'version': '1.0.0' },
|
||||
@@ -254,7 +264,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -274,10 +284,12 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should position custom extensions correctly in schema objects', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
'openapi': '3.0.0',
|
||||
'info': { 'title': 'Test API', 'version': '1.0.0' },
|
||||
@@ -295,7 +307,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -318,10 +330,12 @@ describe('Custom Extensions Support', () => {
|
||||
|
||||
describe('Unknown key handling', () => {
|
||||
it('should sort unknown keys alphabetically at the end', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
'openapi': '3.0.0',
|
||||
'info': { 'title': 'Test API', 'version': '1.0.0' },
|
||||
@@ -332,7 +346,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -358,10 +372,12 @@ describe('Custom Extensions Support', () => {
|
||||
});
|
||||
|
||||
it('should handle mixed custom extensions and unknown keys', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
'openapi': '3.0.0',
|
||||
'info': { 'title': 'Test API', 'version': '1.0.0' },
|
||||
@@ -374,7 +390,7 @@ describe('Custom Extensions Support', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import { parsers } from '../src/index';
|
||||
|
||||
describe('File Detection Tests', () => {
|
||||
it('should detect OpenAPI root files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
it('should detect OpenAPI root files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const testYaml = `openapi: 3.0.0
|
||||
const testYaml = `openapi: 3.0.0
|
||||
info:
|
||||
title: Test API
|
||||
version: 1.0.0
|
||||
@@ -17,20 +17,20 @@ paths:
|
||||
'200':
|
||||
description: Success`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(testYaml, { filepath: 'openapi.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.openapi).toBe('3.0.0');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(testYaml, { filepath: 'openapi.yaml' });
|
||||
|
||||
it('should detect partial schema files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.openapi).toBe('3.0.0');
|
||||
});
|
||||
|
||||
const schemaYaml = `type: object
|
||||
it('should detect partial schema files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const schemaYaml = `type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
@@ -40,40 +40,40 @@ required:
|
||||
- id
|
||||
- name`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.type).toBe('object');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' });
|
||||
|
||||
it('should detect parameter files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.type).toBe('object');
|
||||
});
|
||||
|
||||
const parameterYaml = `name: id
|
||||
it('should detect parameter files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const parameterYaml = `name: id
|
||||
in: path
|
||||
required: true
|
||||
description: User ID
|
||||
schema:
|
||||
type: integer`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.name).toBe('id');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' });
|
||||
|
||||
it('should detect response files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.name).toBe('id');
|
||||
});
|
||||
|
||||
const responseYaml = `description: User response
|
||||
it('should detect response files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const responseYaml = `description: User response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -84,20 +84,20 @@ content:
|
||||
name:
|
||||
type: string`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.description).toBe('User response');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' });
|
||||
|
||||
it('should detect path files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.description).toBe('User response');
|
||||
});
|
||||
|
||||
const pathYaml = `get:
|
||||
it('should detect path files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const pathYaml = `get:
|
||||
summary: Get users
|
||||
responses:
|
||||
'200':
|
||||
@@ -110,91 +110,100 @@ post:
|
||||
schema:
|
||||
type: object`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(pathYaml, { filepath: 'paths/users.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.get).toBeDefined();
|
||||
expect(result?.content.post).toBeDefined();
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(pathYaml, { filepath: 'paths/users.yaml' });
|
||||
|
||||
it('should detect security scheme files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.get).toBeDefined();
|
||||
expect(result?.content.post).toBeDefined();
|
||||
});
|
||||
|
||||
const securityYaml = `type: http
|
||||
it('should detect security scheme files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const securityYaml = `type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: JWT authentication`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' });
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.type).toBe('http');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' });
|
||||
|
||||
it('should reject non-OpenAPI files', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
expect(result?.content.type).toBe('http');
|
||||
});
|
||||
|
||||
const nonOpenAPIYaml = `name: John
|
||||
it('should handle non-OpenAPI files', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const nonOpenAPIYaml = `name: John
|
||||
age: 30
|
||||
city: New York`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
expect(() => parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' })).toThrow('Not an OpenAPI file');
|
||||
});
|
||||
const parsedYaml = {
|
||||
name: 'John',
|
||||
age: 30,
|
||||
city: 'New York'
|
||||
}
|
||||
|
||||
it('should accept files in OpenAPI directories even with simple content', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsedData = parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' })
|
||||
|
||||
const simpleYaml = `name: John
|
||||
expect(parsedData).toBeDefined()
|
||||
expect(parsedData?.isOpenAPI).toBeFalse();
|
||||
});
|
||||
|
||||
it('should accept files in OpenAPI directories even with simple content', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const simpleYaml = `name: John
|
||||
age: 30
|
||||
city: New York`;
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(simpleYaml, { filepath: 'components/schemas/User.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
});
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(simpleYaml, { filepath: 'components/schemas/User.yaml' });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
});
|
||||
|
||||
it('should support component directory patterns', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
it('should support component directory patterns', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const componentYaml = `type: object
|
||||
const componentYaml = `type: object
|
||||
properties:
|
||||
message:
|
||||
type: string`;
|
||||
|
||||
// Test various component directory patterns
|
||||
const paths = [
|
||||
'components/schemas/Error.yaml',
|
||||
'components/parameters/CommonPagination.yaml',
|
||||
'components/responses/ErrorResponse.yaml',
|
||||
'components/requestBodies/UserCreateBody.yaml',
|
||||
'components/headers/RateLimitHeaders.yaml',
|
||||
'components/examples/UserExample.yaml',
|
||||
'components/securitySchemes/BearerAuth.yaml',
|
||||
'components/links/UserCreatedLink.yaml',
|
||||
'components/callbacks/NewMessageCallback.yaml',
|
||||
'webhooks/messageCreated.yaml',
|
||||
'paths/users.yaml'
|
||||
];
|
||||
// Test various component directory patterns
|
||||
const paths = [
|
||||
'components/schemas/Error.yaml',
|
||||
'components/parameters/CommonPagination.yaml',
|
||||
'components/responses/ErrorResponse.yaml',
|
||||
'components/requestBodies/UserCreateBody.yaml',
|
||||
'components/headers/RateLimitHeaders.yaml',
|
||||
'components/examples/UserExample.yaml',
|
||||
'components/securitySchemes/BearerAuth.yaml',
|
||||
'components/links/UserCreatedLink.yaml',
|
||||
'components/callbacks/NewMessageCallback.yaml',
|
||||
'webhooks/messageCreated.yaml',
|
||||
'paths/users.yaml'
|
||||
];
|
||||
|
||||
paths.forEach(path => {
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(componentYaml, { filepath: path });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.type).toBe('openapi');
|
||||
expect(result?.format).toBe('yaml');
|
||||
paths.forEach(path => {
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = parser?.parse(componentYaml, { filepath: path });
|
||||
expect(result).toBeDefined();
|
||||
expect(result?.isOpenAPI).toBeTrue();
|
||||
expect(result?.format).toBe('yaml');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import {parsers, printers} from '../src/index';
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
describe('Real OpenAPI file processing', () => {
|
||||
@@ -213,18 +213,18 @@ describe('Integration Tests', () => {
|
||||
]
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: openApiContent,
|
||||
originalText: '',
|
||||
format: 'json'
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (result && typeof result === 'string') {
|
||||
@@ -365,18 +365,18 @@ describe('Integration Tests', () => {
|
||||
]
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: swaggerContent,
|
||||
originalText: '',
|
||||
format: 'json'
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (result && typeof result === 'string') {
|
||||
@@ -419,18 +419,18 @@ describe('Integration Tests', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: yamlContent,
|
||||
originalText: '',
|
||||
format: 'yaml'
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (result) {
|
||||
@@ -451,7 +451,7 @@ describe('Integration Tests', () => {
|
||||
|
||||
describe('Error handling', () => {
|
||||
it('should handle malformed JSON gracefully', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const malformedJson = '{"openapi": "3.0.0", "info": {';
|
||||
@@ -461,7 +461,7 @@ describe('Integration Tests', () => {
|
||||
});
|
||||
|
||||
it('should handle malformed YAML gracefully', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const malformedYaml = 'openapi: 3.0.0\ninfo:\n title: Test\n version: 1.0.0\n invalid: [';
|
||||
@@ -470,14 +470,23 @@ describe('Integration Tests', () => {
|
||||
expect(() => parser?.parse(malformedYaml, { filepath: 'test.yaml' })).toThrow();
|
||||
});
|
||||
|
||||
it('should reject non-OpenAPI content', () => {
|
||||
const parser = plugin.parsers?.['openapi-parser'];
|
||||
it('should handle non-OpenAPI content', () => {
|
||||
const parser = parsers?.['openapi-parser'];
|
||||
expect(parser).toBeDefined();
|
||||
|
||||
const nonOpenAPI = '{"name": "John", "age": 30}';
|
||||
|
||||
const parsedJSON = {
|
||||
name: 'John',
|
||||
age: 30
|
||||
}
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
expect(() => parser?.parse(nonOpenAPI, { filepath: 'test.json' })).toThrow('Not an OpenAPI file');
|
||||
const parsedData = parser?.parse(nonOpenAPI, { filepath: 'test.json' })
|
||||
expect(parsedData).toBeDefined();
|
||||
expect(parsedData?.isOpenAPI).toBeFalse();
|
||||
expect(parsedData?.content).toBeDefined();
|
||||
expect(parsedData?.content).toEqual(parsedJSON);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -506,11 +515,11 @@ describe('Integration Tests', () => {
|
||||
};
|
||||
}
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
content: largeOpenAPI,
|
||||
originalText: '',
|
||||
format: 'json'
|
||||
@@ -519,7 +528,7 @@ describe('Integration Tests', () => {
|
||||
const startTime = Date.now();
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import { printers } from '../src/index';
|
||||
|
||||
describe('Key Ordering Tests', () => {
|
||||
describe('Info section key ordering', () => {
|
||||
it('should sort info keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
@@ -21,10 +22,9 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
},
|
||||
originalText: '',
|
||||
format: 'json'
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -32,7 +32,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
// Check that info keys appear in the correct order
|
||||
const titleIndex = resultString.indexOf('title');
|
||||
const versionIndex = resultString.indexOf('version');
|
||||
@@ -51,11 +51,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Operation key ordering', () => {
|
||||
it('should sort operation keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
type: 'openapi',
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -77,12 +78,11 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
originalText: '',
|
||||
format: 'json'
|
||||
originalText: ''
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -90,7 +90,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
// Check that operation keys appear in the correct order
|
||||
const summaryIndex = resultString.indexOf('summary');
|
||||
const operationIdIndex = resultString.indexOf('operationId');
|
||||
@@ -119,10 +119,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Schema key ordering', () => {
|
||||
it('should sort schema keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -169,7 +171,8 @@ describe('Key Ordering Tests', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
console.log('result', result);
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -177,7 +180,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
const typeIndex = resultString.indexOf('type:');
|
||||
const formatIndex = resultString.indexOf('format:');
|
||||
const titleIndex = resultString.indexOf('title:');
|
||||
@@ -247,10 +250,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Response key ordering', () => {
|
||||
it('should sort response keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -271,7 +276,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -294,10 +299,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Parameter key ordering', () => {
|
||||
it('should sort parameter keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -327,7 +334,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -335,7 +342,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
const nameIndex = resultString.indexOf('name:');
|
||||
const inIndex = resultString.indexOf('in:');
|
||||
const descriptionIndex = resultString.indexOf('description:');
|
||||
@@ -366,10 +373,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Security scheme key ordering', () => {
|
||||
it('should sort security scheme keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -395,7 +404,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -426,10 +435,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Server key ordering', () => {
|
||||
it('should sort server keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -442,8 +453,8 @@ describe('Key Ordering Tests', () => {
|
||||
]
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -451,7 +462,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
const nameIndex = resultString.indexOf('name');
|
||||
const descriptionIndex = resultString.indexOf('description');
|
||||
const urlIndex = resultString.indexOf('url');
|
||||
@@ -465,10 +476,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Tag key ordering', () => {
|
||||
it('should sort tag keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -481,8 +494,8 @@ describe('Key Ordering Tests', () => {
|
||||
]
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -490,7 +503,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
const nameIndex = resultString.indexOf('name');
|
||||
const descriptionIndex = resultString.indexOf('description');
|
||||
const externalDocsIndex = resultString.indexOf('externalDocs');
|
||||
@@ -502,10 +515,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('External docs key ordering', () => {
|
||||
it('should sort external docs keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -515,8 +530,8 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -524,7 +539,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
// Check that external docs keys appear in the correct order
|
||||
const descriptionIndex = resultString.indexOf('description');
|
||||
const urlIndex = resultString.indexOf('url');
|
||||
@@ -535,10 +550,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Path sorting', () => {
|
||||
it('should sort paths by specificity', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -550,8 +567,8 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -559,7 +576,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
// Check that paths are sorted by specificity (fewer parameters first)
|
||||
const usersIndex = resultString.indexOf('/users');
|
||||
const usersIdIndex = resultString.indexOf('/users/{id}');
|
||||
@@ -574,10 +591,12 @@ describe('Key Ordering Tests', () => {
|
||||
|
||||
describe('Response code sorting', () => {
|
||||
it('should sort response codes numerically', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -596,8 +615,8 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 });
|
||||
// @ts-expect-error We are mocking things here so we don't need to pass a print function
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -605,7 +624,7 @@ describe('Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
|
||||
// Check that response codes are sorted numerically
|
||||
const code200Index = resultString.indexOf('200');
|
||||
const code400Index = resultString.indexOf('400');
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
|
||||
describe('Plugin Options', () => {
|
||||
it('should use custom tabWidth for JSON formatting', () => {
|
||||
const testData = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Test',
|
||||
version: '1.0.0'
|
||||
},
|
||||
paths: {}
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, { tabWidth: 4 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Result is undefined');
|
||||
}
|
||||
|
||||
// Check that 4-space indentation is used
|
||||
expect(result).toContain(' "openapi"');
|
||||
expect(result).toContain(' "info"');
|
||||
expect(result).toContain(' "title"');
|
||||
expect(result).toContain(' "version"');
|
||||
});
|
||||
|
||||
it('should use custom tabWidth for YAML formatting', () => {
|
||||
const testData = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Test',
|
||||
version: '1.0.0'
|
||||
},
|
||||
paths: {}
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { tabWidth: 4 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Result is undefined');
|
||||
}
|
||||
|
||||
// Check that 4-space indentation is used
|
||||
expect(result).toContain(' title: Test');
|
||||
expect(result).toContain(' version: 1.0.0');
|
||||
});
|
||||
|
||||
it('should use default tabWidth when not specified', () => {
|
||||
const testData = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'Test',
|
||||
version: '1.0.0'
|
||||
},
|
||||
paths: {}
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, {}, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Result is undefined');
|
||||
}
|
||||
|
||||
// Check that 2-space indentation is used (default)
|
||||
expect(result).toContain(' "openapi"');
|
||||
expect(result).toContain(' "info"');
|
||||
expect(result).toContain(' "title"');
|
||||
expect(result).toContain(' "version"');
|
||||
});
|
||||
|
||||
it('should use custom printWidth for YAML formatting', () => {
|
||||
const testData = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'This is a very long title that should be wrapped according to printWidth',
|
||||
version: '1.0.0'
|
||||
},
|
||||
paths: {}
|
||||
};
|
||||
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { printWidth: 20 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
// The YAML should be formatted with the custom line width
|
||||
expect(result).toBeDefined();
|
||||
// Note: js-yaml doesn't always respect lineWidth for all content types,
|
||||
// but we can verify the option is passed through
|
||||
});
|
||||
});
|
||||
@@ -32,13 +32,13 @@ describe('Prettier OpenAPI Plugin', () => {
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
|
||||
expect(parsed).toBeDefined();
|
||||
expect(parsed?.type).toBe('openapi');
|
||||
expect(parsed?.isOpenAPI).toBeTrue();
|
||||
expect(parsed?.format).toBe('json');
|
||||
expect(parsed?.content).toBeDefined();
|
||||
|
||||
// Format the parsed content
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('"openapi"');
|
||||
@@ -80,13 +80,13 @@ openapi: 3.0.0`;
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsed = parser?.parse(inputYaml, { filepath: 'test.yaml' });
|
||||
expect(parsed).toBeDefined();
|
||||
expect(parsed?.type).toBe('openapi');
|
||||
expect(parsed?.isOpenAPI).toBeTrue();
|
||||
expect(parsed?.format).toBe('yaml');
|
||||
expect(parsed?.content).toBeDefined();
|
||||
|
||||
// Format the parsed content
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('openapi:');
|
||||
@@ -148,13 +148,13 @@ openapi: 3.0.0`;
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
|
||||
expect(parsed).toBeDefined();
|
||||
expect(parsed?.type).toBe('openapi');
|
||||
expect(parsed?.isOpenAPI).toBeTrue();
|
||||
expect(parsed?.format).toBe('json');
|
||||
expect(parsed?.content).toBeDefined();
|
||||
|
||||
// Format the parsed content
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('"swagger"');
|
||||
@@ -201,13 +201,13 @@ describe('Key Ordering Tests', () => {
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
|
||||
expect(parsed).toBeDefined();
|
||||
expect(parsed?.type).toBe('openapi');
|
||||
expect(parsed?.isOpenAPI).toBeTrue();
|
||||
expect(parsed?.format).toBe('json');
|
||||
expect(parsed?.content).toBeDefined();
|
||||
|
||||
// Format the parsed content
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
||||
@@ -261,13 +261,13 @@ describe('Key Ordering Tests', () => {
|
||||
// @ts-expect-error We are mocking things here
|
||||
const parsed = parser?.parse(inputJson, { filepath: 'test.json' });
|
||||
expect(parsed).toBeDefined();
|
||||
expect(parsed?.type).toBe('openapi');
|
||||
expect(parsed?.isOpenAPI).toBeTrue();
|
||||
expect(parsed?.format).toBe('json');
|
||||
expect(parsed?.content).toBeDefined();
|
||||
|
||||
// Format the parsed content
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => '');
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import plugin from '../src/index';
|
||||
import { printers } from '../src/index';
|
||||
|
||||
describe('Simple Key Ordering Tests', () => {
|
||||
it('should sort top-level OpenAPI keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'yaml',
|
||||
content: {
|
||||
paths: { '/test': { get: {} } },
|
||||
components: { schemas: {} },
|
||||
@@ -19,7 +21,7 @@ describe('Simple Key Ordering Tests', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -46,10 +48,12 @@ describe('Simple Key Ordering Tests', () => {
|
||||
});
|
||||
|
||||
it('should sort operation keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: { title: 'Test API', version: '1.0.0' },
|
||||
@@ -74,7 +78,7 @@ describe('Simple Key Ordering Tests', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -109,10 +113,12 @@ describe('Simple Key Ordering Tests', () => {
|
||||
});
|
||||
|
||||
it('should sort info keys correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
@@ -127,7 +133,7 @@ describe('Simple Key Ordering Tests', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
@@ -135,8 +141,6 @@ describe('Simple Key Ordering Tests', () => {
|
||||
}
|
||||
|
||||
const resultString = result.toString();
|
||||
|
||||
console.log(resultString);
|
||||
|
||||
// Check that info keys appear in the correct order
|
||||
const titleIndex = resultString.indexOf('title');
|
||||
@@ -154,10 +158,12 @@ describe('Simple Key Ordering Tests', () => {
|
||||
});
|
||||
|
||||
it('should handle custom extensions correctly', () => {
|
||||
const printer = plugin.printers?.['openapi-ast'];
|
||||
const printer = printers?.['openapi-ast'];
|
||||
expect(printer).toBeDefined();
|
||||
|
||||
const testData = {
|
||||
isOpenAPI: true,
|
||||
format: 'json',
|
||||
content: {
|
||||
'x-custom-field': 'value',
|
||||
'openapi': '3.0.0',
|
||||
@@ -168,7 +174,8 @@ describe('Simple Key Ordering Tests', () => {
|
||||
};
|
||||
|
||||
// @ts-expect-error We are mocking things here
|
||||
const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => '');
|
||||
const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => '');
|
||||
console.log('result', result);
|
||||
expect(result).toBeDefined();
|
||||
|
||||
if (!result) {
|
||||
|
||||
Reference in New Issue
Block a user