From 9a8cfccfc8b0379b9b640e666714f8b1cdda9323 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Fri, 15 Dec 2023 12:27:52 +0800 Subject: [PATCH] :sparkles: feat: support openapi convertor --- .prettierignore | 2 +- package.json | 4 + src/index.ts | 1 + src/openapi/index.ts | 222 +++++ tests/__snapshots__/openapi.test.ts.snap | 333 +++++++ tests/fixtures/OpenAPI_Auth_API_Key.json | 147 ++++ tests/fixtures/OpenAPI_V2.json | 1012 ++++++++++++++++++++++ tests/fixtures/OpenAPI_V3.json | 245 ++++++ tests/fixtures/OpenAPI_V3_0_2.json | 215 +++++ tests/openapi.test.ts | 197 +++++ tsconfig.json | 6 +- 11 files changed, 2381 insertions(+), 3 deletions(-) create mode 100644 src/openapi/index.ts create mode 100644 tests/__snapshots__/openapi.test.ts.snap create mode 100644 tests/fixtures/OpenAPI_Auth_API_Key.json create mode 100644 tests/fixtures/OpenAPI_V2.json create mode 100644 tests/fixtures/OpenAPI_V3.json create mode 100644 tests/fixtures/OpenAPI_V3_0_2.json create mode 100644 tests/openapi.test.ts diff --git a/.prettierignore b/.prettierignore index 51fdf94..5d9a253 100644 --- a/.prettierignore +++ b/.prettierignore @@ -29,7 +29,7 @@ coverage .eslintcache .stylelintcache test-output -__snapshots__ +tests/__snapshots__ *.snap # production diff --git a/package.json b/package.json index 9639d79..fb40914 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,12 @@ ] }, "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", "@babel/runtime": "^7.23.2", "@types/json-schema": "^7.0.14", + "openapi-jsonschema-parameters": "^12.1.3", + "openapi-types": "^12.1.3", + "swagger-client": "^3.24.6", "zod": "^3.22.4" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 7a3b1a3..a722871 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './error'; +export * from './openapi'; export * from './request'; export * from './schema/manifest'; export * from './schema/market'; diff --git a/src/openapi/index.ts b/src/openapi/index.ts new file mode 100644 index 0000000..bccdab7 --- /dev/null +++ b/src/openapi/index.ts @@ -0,0 +1,222 @@ +import SwaggerParser from '@apidevtools/swagger-parser'; +import { convertParametersToJSONSchema } from 'openapi-jsonschema-parameters'; +import { OpenAPI, OpenAPIV3_1 } from 'openapi-types'; + +import { pluginApiSchema } from '../schema/manifest'; +import { LobeChatPluginApi, PluginSchema } from '../types'; + +export class OpenAPIConvertor { + private readonly openapi: object; + constructor(openapi: object) { + this.openapi = openapi; + } + + convertOpenAPIToPluginSchema = async () => { + const api = await SwaggerParser.dereference(this.openapi as OpenAPI.Document); + + const paths = api.paths!; + const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']; + + const plugins: LobeChatPluginApi[] = []; + + for (const [path, operations] of Object.entries(paths)) { + for (const method of methods) { + const operation = (operations as any)[method]; + if (operation) { + const parametersSchema = convertParametersToJSONSchema(operation.parameters || []); + const requestBodySchema = this.convertRequestBodyToSchema(operation.requestBody); + + const parameters = this.mergeSchemas( + ...Object.values(parametersSchema), + requestBodySchema, + ); + + // 保留原始逻辑作为备选 + const name = operation.operationId || `${method.toUpperCase()} ${path}`; + + const description = operation.summary || operation.description || name; + + const plugin = { description, name, parameters } as LobeChatPluginApi; + + const res = pluginApiSchema.safeParse(plugin); + if (res.success) plugins.push(plugin); + else { + throw res.error; + } + } + } + } + + return plugins; + }; + + convertAuthToSettingsSchema = async ( + // eslint-disable-next-line unicorn/no-object-as-default-parameter + rawSettingsSchema: PluginSchema = { properties: {}, type: 'object' }, + ): Promise => { + let settingsSchema = rawSettingsSchema; + + // @ts-ignore + const { default: SwaggerClient } = await import('swagger-client'); + + // 使用 SwaggerClient 解析 OpenAPI JSON + const openAPI = await SwaggerClient.resolve({ spec: this.openapi }); + const api = openAPI.spec; + + for (const entry of Object.entries(api.components?.securitySchemes || {})) { + let authSchema = {} as PluginSchema; + const [key, value] = entry as [string, any]; + + switch (value.type) { + case 'apiKey': { + authSchema = { + properties: { + [key]: { + description: value.description || `${key} API Key`, + format: 'password', + title: value.name, + type: 'string', + }, + }, + required: [key], + type: 'object', + }; + break; + } + case 'http': { + if (value.scheme === 'basic') { + authSchema = { + properties: { + [key]: { + description: 'Basic authentication credentials', + format: 'password', + type: 'string', + }, + }, + required: [key], + type: 'object', + }; + } else if (value.scheme === 'bearer') { + authSchema = { + properties: { + [key]: { + description: value.description || `${key} Bearer token`, + format: 'password', + title: key, + type: 'string', + }, + }, + required: [key], + type: 'object', + }; + } + break; + } + case 'oauth2': { + authSchema = { + properties: { + [`${key}_clientId`]: { + description: 'Client ID for OAuth2', + type: 'string', + }, + [`${key}_clientSecret`]: { + description: 'Client Secret for OAuth2', + format: 'password', + type: 'string', + }, + [`${key}_accessToken`]: { + description: 'Access token for OAuth2', + format: 'password', + type: 'string', + }, + }, + required: [`${key}_clientId`, `${key}_clientSecret`, `${key}_accessToken`], + type: 'object', + }; + break; + } + } + + // 合并当前鉴权机制的 schema 到 settingsSchema + Object.assign(settingsSchema.properties, authSchema.properties); + + if (authSchema.required) { + settingsSchema.required = [ + ...new Set([...(settingsSchema.required || []), ...authSchema.required]), + ]; + } + } + + return settingsSchema; + }; + + private convertRequestBodyToSchema(requestBody: OpenAPIV3_1.RequestBodyObject) { + if (!requestBody || !requestBody.content) { + return null; + } + + let requestBodySchema = {}; + + // 遍历所有的 content-type + for (const [contentType, mediaType] of Object.entries(requestBody.content)) { + if (mediaType.schema) { + // 直接使用已解析的 Schema + const resolvedSchema = mediaType.schema; + + // 根据不同的 content-type,可以在这里添加特定的处理逻辑 + switch (contentType) { + case 'application/json': { + // 直接使用解析后的 Schema 作为 JSON 的请求体定义 + requestBodySchema = resolvedSchema; + break; + } + case 'application/x-www-form-urlencoded': + case 'multipart/form-data': { + // 这些类型通常用于文件上传和表单数据,可能需要特别处理 + requestBodySchema = resolvedSchema; + break; + } + // 其他 MIME 类型... + default: { + // 如果遇到未知的 content-type,可以选择忽略或抛出错误 + console.warn(`Unsupported content-type: ${contentType}`); + break; + } + } + } + } + + return requestBodySchema; + } + + private mergeSchemas(...schemas: any[]) { + // 初始化合并后的 Schema + const mergedSchema: PluginSchema = { + properties: {}, + required: [], + type: 'object', + }; + + // 遍历每个参数的 Schema + for (const schema of schemas) { + if (schema && schema.properties) { + // 合并属性 + Object.assign(mergedSchema.properties, schema.properties); + + // 合并必需字段 + if (Array.isArray(schema.required)) { + mergedSchema.required = [ + ...new Set([...(mergedSchema.required || []), ...schema.required]), + ]; + } + } + } + + // 如果没有任何必需字段,则删除 required 属性 + if (mergedSchema.required?.length === 0) { + delete mergedSchema.required; + } + + return mergedSchema; + } +} diff --git a/tests/__snapshots__/openapi.test.ts.snap b/tests/__snapshots__/openapi.test.ts.snap new file mode 100644 index 0000000..700119a --- /dev/null +++ b/tests/__snapshots__/openapi.test.ts.snap @@ -0,0 +1,333 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`OpenAPIConvertor > convertOpenAPIToPluginSchema > can convert OpenAPI v2 MJ openAPI 1`] = ` +[ + { + "description": "查询所有账号", + "name": "listUsingGET", + "parameters": { + "properties": {}, + "type": "object", + }, + }, + { + "description": "指定ID获取账号", + "name": "fetchUsingGET", + "parameters": { + "properties": { + "id": { + "description": "账号ID", + "type": "string", + }, + }, + "type": "object", + }, + }, + { + "description": "提交Blend任务", + "name": "blendUsingPOST", + "parameters": { + "properties": { + "base64Array": { + "description": "图片base64数组", + "example": [ + "data:image/png;base64,xxx1", + "data:image/png;base64,xxx2", + ], + "items": { + "type": "string", + }, + "refType": "string", + "type": "array", + }, + "dimensions": { + "description": "比例: PORTRAIT(2:3); SQUARE(1:1); LANDSCAPE(3:2)", + "enum": [ + "PORTRAIT", + "SQUARE", + "LANDSCAPE", + ], + "example": "SQUARE", + "refType": null, + "type": "string", + }, + "notifyHook": { + "description": "回调地址, 为空时使用全局notifyHook", + "refType": null, + "type": "string", + }, + "state": { + "description": "自定义参数", + "refType": null, + "type": "string", + }, + }, + "required": [ + "base64Array", + ], + "type": "object", + }, + }, + { + "description": "绘图变化", + "name": "changeUsingPOST", + "parameters": { + "properties": { + "action": { + "description": "UPSCALE(放大); VARIATION(变换); REROLL(重新生成)", + "enum": [ + "UPSCALE", + "VARIATION", + "REROLL", + ], + "example": "UPSCALE", + "type": "string", + }, + "index": { + "description": "序号(1~4), action为UPSCALE,VARIATION时必传", + "example": 1, + "exclusiveMaximum": false, + "exclusiveMinimum": false, + "format": "int32", + "maximum": 4, + "minimum": 1, + "type": "integer", + }, + "notifyHook": { + "description": "回调地址, 为空时使用全局notifyHook", + "type": "string", + }, + "state": { + "description": "自定义参数", + "type": "string", + }, + "taskId": { + "description": "任务ID", + "example": "1320098173412546", + "type": "string", + }, + }, + "required": [ + "action", + "taskId", + ], + "type": "object", + }, + }, + { + "description": "提交Describe任务", + "name": "describeUsingPOST", + "parameters": { + "properties": { + "base64": { + "description": "图片base64", + "example": "data:image/png;base64,xxx", + "type": "string", + }, + "notifyHook": { + "description": "回调地址, 为空时使用全局notifyHook", + "type": "string", + }, + "state": { + "description": "自定义参数", + "type": "string", + }, + }, + "required": [ + "base64", + ], + "type": "object", + }, + }, + { + "description": "提交Imagine任务", + "name": "imagineUsingPOST", + "parameters": { + "properties": { + "base64Array": { + "description": "垫图base64数组", + "items": { + "type": "string", + }, + "type": "array", + }, + "notifyHook": { + "description": "回调地址, 为空时使用全局notifyHook", + "type": "string", + }, + "prompt": { + "description": "提示词", + "example": "Cat", + "type": "string", + }, + "state": { + "description": "自定义参数", + "type": "string", + }, + }, + "required": [ + "prompt", + ], + "type": "object", + }, + }, + { + "description": "绘图变化-simple", + "name": "simpleChangeUsingPOST", + "parameters": { + "properties": { + "content": { + "description": "变化描述: ID $action$index", + "example": "1320098173412546 U2", + "type": "string", + }, + "notifyHook": { + "description": "回调地址, 为空时使用全局notifyHook", + "type": "string", + }, + "state": { + "description": "自定义参数", + "type": "string", + }, + }, + "required": [ + "content", + ], + "type": "object", + }, + }, + { + "description": "查询所有任务", + "name": "listUsingGET_1", + "parameters": { + "properties": {}, + "type": "object", + }, + }, + { + "description": "根据ID列表查询任务", + "name": "listByIdsUsingPOST", + "parameters": { + "properties": { + "ids": { + "items": { + "type": "string", + }, + "type": "array", + }, + }, + "type": "object", + }, + }, + { + "description": "查询任务队列", + "name": "queueUsingGET", + "parameters": { + "properties": {}, + "type": "object", + }, + }, + { + "description": "指定ID获取任务", + "name": "fetchUsingGET_1", + "parameters": { + "properties": { + "id": { + "description": "任务ID", + "type": "string", + }, + }, + "type": "object", + }, + }, +] +`; + +exports[`OpenAPIConvertor > convertOpenAPIToPluginSchema > can convert OpenAPI v3.1 to lobe apis 1`] = ` +[ + { + "description": "Read Course Segments", + "name": "read_course_segments_course_segments__get", + "parameters": { + "properties": {}, + "type": "object", + }, + }, + { + "description": "Read Problem Set Item", + "name": "read_problem_set_item_problem_set__problem_set_id___question_number__get", + "parameters": { + "properties": { + "problem_set_id": { + "title": "Problem Set Id", + "type": "integer", + }, + "question_number": { + "title": "Question Number", + "type": "integer", + }, + }, + "required": [ + "problem_set_id", + "question_number", + ], + "type": "object", + }, + }, + { + "description": "Read Random Problem Set Items", + "name": "read_random_problem_set_items_problem_set_random__problem_set_id___n_items__get", + "parameters": { + "properties": { + "n_items": { + "title": "N Items", + "type": "integer", + }, + "problem_set_id": { + "title": "Problem Set Id", + "type": "integer", + }, + }, + "required": [ + "problem_set_id", + "n_items", + ], + "type": "object", + }, + }, + { + "description": "Read Range Of Problem Set Items", + "name": "read_range_of_problem_set_items_problem_set_range__problem_set_id___start___end__get", + "parameters": { + "properties": { + "end": { + "title": "End", + "type": "integer", + }, + "problem_set_id": { + "title": "Problem Set Id", + "type": "integer", + }, + "start": { + "title": "Start", + "type": "integer", + }, + }, + "required": [ + "problem_set_id", + "start", + "end", + ], + "type": "object", + }, + }, + { + "description": "Read User Id", + "name": "read_user_id_user__get", + "parameters": { + "properties": {}, + "type": "object", + }, + }, +] +`; diff --git a/tests/fixtures/OpenAPI_Auth_API_Key.json b/tests/fixtures/OpenAPI_Auth_API_Key.json new file mode 100644 index 0000000..5ed9e0e --- /dev/null +++ b/tests/fixtures/OpenAPI_Auth_API_Key.json @@ -0,0 +1,147 @@ +{ + "components": { + "schemas": { + "Vendor": { + "type": "string", + "enum": ["visa", "master-card", "diners-club", "american-express", "discover", "jcb"] + }, + "GetRandomCardNumberResponse": { + "type": "object", + "properties": { + "vendors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Vendor" + } + } + } + }, + "GetVendorsResponse": { + "type": "object", + "properties": { + "number": { + "type": "string" + }, + "vendor": { + "type": "string", + "items": { + "$ref": "#/components/schemas/Vendor" + } + } + } + }, + "ErrorResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + }, + "securitySchemes": { + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-OpenAPIHub-Key" + } + } + }, + "info": { + "title": "Trial API Credit Card Number Generator", + "description": "# Introduction\nThis is Trial API Credit Card Number Generator", + "version": "1.0.2" + }, + "openapi": "3.0.0", + "paths": { + "/random_card_number": { + "get": { + "operationId": "getRandomCardNumber", + "tags": ["Credit Card Number Generator"], + "summary": "It is API to generate random credit card numbers", + "description": "Generate random credit card numbers", + "parameters": [ + { + "name": "vendor", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/Vendor" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetRandomCardNumberResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + }, + "/vendors": { + "get": { + "operationId": "getSupportedVendors", + "tags": ["Credit Card Number Generator"], + "summary": "It is API to get list of supported vendors", + "description": "Return list of supported vendors", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetVendorsResponse" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "apiKeyAuth": [] + } + ] + } + } + }, + "servers": [ + { + "url": "https://trial-api-credit-card-number-generator-2m83.gw.openapihub.com" + }, + { + "url": "https://5953f2e3-b62c-40cf-bd13-6ffa26e0dd4b-2m83.gw.openapihub.com" + } + ], + "tags": [] +} diff --git a/tests/fixtures/OpenAPI_V2.json b/tests/fixtures/OpenAPI_V2.json new file mode 100644 index 0000000..033805a --- /dev/null +++ b/tests/fixtures/OpenAPI_V2.json @@ -0,0 +1,1012 @@ +{ + "basePath": "/mj", + "definitions": { + "Blend提交参数": { + "type": "object", + "required": ["base64Array"], + "properties": { + "base64Array": { + "type": "array", + "example": ["data:image/png;base64,xxx1", "data:image/png;base64,xxx2"], + "description": "图片base64数组", + "items": { + "type": "string" + }, + "refType": "string" + }, + "dimensions": { + "type": "string", + "example": "SQUARE", + "description": "比例: PORTRAIT(2:3); SQUARE(1:1); LANDSCAPE(3:2)", + "enum": ["PORTRAIT", "SQUARE", "LANDSCAPE"], + "refType": null + }, + "notifyHook": { + "type": "string", + "description": "回调地址, 为空时使用全局notifyHook", + "refType": null + }, + "state": { + "type": "string", + "description": "自定义参数", + "refType": null + } + }, + "title": "Blend提交参数" + }, + "Describe提交参数": { + "type": "object", + "required": ["base64"], + "properties": { + "base64": { + "type": "string", + "example": "data:image/png;base64,xxx", + "description": "图片base64" + }, + "notifyHook": { + "type": "string", + "description": "回调地址, 为空时使用全局notifyHook" + }, + "state": { + "type": "string", + "description": "自定义参数" + } + }, + "title": "Describe提交参数" + }, + "Discord账号": { + "type": "object", + "properties": { + "channelId": { + "type": "string", + "description": "频道ID" + }, + "coreSize": { + "type": "integer", + "format": "int32", + "description": "并发数" + }, + "enable": { + "type": "boolean", + "description": "是否可用" + }, + "guildId": { + "type": "string", + "description": "服务器ID" + }, + "id": { + "type": "string", + "description": "ID" + }, + "properties": { + "type": "object" + }, + "queueSize": { + "type": "integer", + "format": "int32", + "description": "等待队列长度" + }, + "timeoutMinutes": { + "type": "integer", + "format": "int32", + "description": "任务超时时间(分钟)" + }, + "userAgent": { + "type": "string", + "description": "用户UserAgent" + }, + "userToken": { + "type": "string", + "description": "用户Token" + } + }, + "title": "Discord账号" + }, + "Imagine提交参数": { + "type": "object", + "required": ["prompt"], + "properties": { + "base64Array": { + "type": "array", + "description": "垫图base64数组", + "items": { + "type": "string" + } + }, + "notifyHook": { + "type": "string", + "description": "回调地址, 为空时使用全局notifyHook" + }, + "prompt": { + "type": "string", + "example": "Cat", + "description": "提示词" + }, + "state": { + "type": "string", + "description": "自定义参数" + } + }, + "title": "Imagine提交参数" + }, + "任务": { + "type": "object", + "properties": { + "action": { + "type": "string", + "description": "任务类型", + "enum": ["IMAGINE", "UPSCALE", "VARIATION", "REROLL", "DESCRIBE", "BLEND"] + }, + "description": { + "type": "string", + "description": "任务描述" + }, + "failReason": { + "type": "string", + "description": "失败原因" + }, + "finishTime": { + "type": "integer", + "format": "int64", + "description": "结束时间" + }, + "id": { + "type": "string", + "description": "ID" + }, + "imageUrl": { + "type": "string", + "description": "图片url" + }, + "progress": { + "type": "string", + "description": "任务进度" + }, + "prompt": { + "type": "string", + "description": "提示词" + }, + "promptEn": { + "type": "string", + "description": "提示词-英文" + }, + "properties": { + "type": "object" + }, + "startTime": { + "type": "integer", + "format": "int64", + "description": "开始执行时间" + }, + "state": { + "type": "string", + "description": "自定义参数" + }, + "status": { + "type": "string", + "description": "任务状态", + "enum": ["NOT_START", "SUBMITTED", "IN_PROGRESS", "FAILURE", "SUCCESS"] + }, + "submitTime": { + "type": "integer", + "format": "int64", + "description": "提交时间" + } + }, + "title": "任务" + }, + "任务查询参数": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "title": "任务查询参数" + }, + "变化任务提交参数": { + "type": "object", + "required": ["action", "taskId"], + "properties": { + "action": { + "type": "string", + "example": "UPSCALE", + "description": "UPSCALE(放大); VARIATION(变换); REROLL(重新生成)", + "enum": ["UPSCALE", "VARIATION", "REROLL"] + }, + "index": { + "type": "integer", + "format": "int32", + "example": 1, + "description": "序号(1~4), action为UPSCALE,VARIATION时必传", + "minimum": 1, + "maximum": 4, + "exclusiveMinimum": false, + "exclusiveMaximum": false + }, + "notifyHook": { + "type": "string", + "description": "回调地址, 为空时使用全局notifyHook" + }, + "state": { + "type": "string", + "description": "自定义参数" + }, + "taskId": { + "type": "string", + "example": "1320098173412546", + "description": "任务ID" + } + }, + "title": "变化任务提交参数" + }, + "变化任务提交参数-simple": { + "type": "object", + "required": ["content"], + "properties": { + "content": { + "type": "string", + "example": "1320098173412546 U2", + "description": "变化描述: ID $action$index" + }, + "notifyHook": { + "type": "string", + "description": "回调地址, 为空时使用全局notifyHook" + }, + "state": { + "type": "string", + "description": "自定义参数" + } + }, + "title": "变化任务提交参数-simple" + }, + "提交结果": { + "type": "object", + "required": ["code", "description"], + "properties": { + "code": { + "type": "integer", + "format": "int32", + "example": 1, + "description": "状态码: 1(提交成功), 21(已存在), 22(排队中), other(错误)" + }, + "description": { + "type": "string", + "example": "提交成功", + "description": "描述" + }, + "properties": { + "type": "object", + "description": "扩展字段" + }, + "result": { + "type": "string", + "example": 1320098173412546, + "description": "任务ID" + } + }, + "title": "提交结果" + } + }, + "host": "localhost:8080", + "info": { + "description": "代理 MidJourney 的discord频道,实现api形式调用AI绘图", + "version": "v2.5.4", + "title": "Midjourney Proxy API文档", + "termsOfService": "https://github.com/novicezk/midjourney-proxy", + "contact": { + "name": "novicezk", + "url": "https://github.com/novicezk/midjourney-proxy" + } + }, + "paths": { + "/mj/account/list": { + "get": { + "tags": ["账号查询"], + "summary": "查询所有账号", + "operationId": "listUsingGET", + "produces": ["*/*"], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "Discord账号", + "$ref": "#/definitions/Discord账号" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "Discord账号", + "$ref": "#/definitions/Discord账号" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/account/{id}/fetch": { + "get": { + "tags": ["账号查询"], + "summary": "指定ID获取账号", + "operationId": "fetchUsingGET", + "produces": ["*/*"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "账号ID", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "Discord账号", + "$ref": "#/definitions/Discord账号" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "Discord账号", + "$ref": "#/definitions/Discord账号" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/submit/blend": { + "post": { + "tags": ["任务提交"], + "summary": "提交Blend任务", + "operationId": "blendUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "blendDTO", + "description": "blendDTO", + "required": true, + "schema": { + "originalRef": "Blend提交参数", + "$ref": "#/definitions/Blend提交参数" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/submit/change": { + "post": { + "tags": ["任务提交"], + "summary": "绘图变化", + "operationId": "changeUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "changeDTO", + "description": "changeDTO", + "required": true, + "schema": { + "originalRef": "变化任务提交参数", + "$ref": "#/definitions/变化任务提交参数" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/submit/describe": { + "post": { + "tags": ["任务提交"], + "summary": "提交Describe任务", + "operationId": "describeUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "describeDTO", + "description": "describeDTO", + "required": true, + "schema": { + "originalRef": "Describe提交参数", + "$ref": "#/definitions/Describe提交参数" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/submit/imagine": { + "post": { + "tags": ["任务提交"], + "summary": "提交Imagine任务", + "operationId": "imagineUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "imagineDTO", + "description": "imagineDTO", + "required": true, + "schema": { + "originalRef": "Imagine提交参数", + "$ref": "#/definitions/Imagine提交参数" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/submit/simple-change": { + "post": { + "tags": ["任务提交"], + "summary": "绘图变化-simple", + "operationId": "simpleChangeUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "simpleChangeDTO", + "description": "simpleChangeDTO", + "required": true, + "schema": { + "originalRef": "变化任务提交参数-simple", + "$ref": "#/definitions/变化任务提交参数-simple" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "提交结果", + "$ref": "#/definitions/提交结果" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/task/list": { + "get": { + "tags": ["任务查询"], + "summary": "查询所有任务", + "operationId": "listUsingGET_1", + "produces": ["*/*"], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/task/list-by-condition": { + "post": { + "tags": ["任务查询"], + "summary": "根据ID列表查询任务", + "operationId": "listByIdsUsingPOST", + "consumes": ["application/json"], + "produces": ["*/*"], + "parameters": [ + { + "in": "body", + "name": "conditionDTO", + "description": "conditionDTO", + "required": true, + "schema": { + "originalRef": "任务查询参数", + "$ref": "#/definitions/任务查询参数" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/task/queue": { + "get": { + "tags": ["任务查询"], + "summary": "查询任务队列", + "operationId": "queueUsingGET", + "produces": ["*/*"], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + }, + "/mj/task/{id}/fetch": { + "get": { + "tags": ["任务查询"], + "summary": "指定ID获取任务", + "operationId": "fetchUsingGET_1", + "produces": ["*/*"], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "任务ID", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "responsesObject": { + "200": { + "description": "OK", + "schema": { + "originalRef": "任务", + "$ref": "#/definitions/任务" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false, + "x-order": "2147483647" + } + } + }, + "swagger": "2.0", + "tags": [ + { + "name": "任务提交", + "x-order": "2147483647" + }, + { + "name": "任务查询", + "x-order": "2147483647" + }, + { + "name": "账号查询", + "x-order": "2147483647" + } + ], + "x-openapi": { + "x-markdownFiles": null, + "x-setting": { + "language": "zh-CN", + "enableSwaggerModels": true, + "swaggerModelName": "Swagger Models", + "enableReloadCacheParameter": false, + "enableAfterScript": true, + "enableDocumentManage": true, + "enableVersion": false, + "enableRequestCache": true, + "enableFilterMultipartApis": false, + "enableFilterMultipartApiMethodType": "POST", + "enableHost": false, + "enableHostText": "", + "enableDynamicParameter": false, + "enableDebug": true, + "enableFooter": true, + "enableFooterCustom": false, + "footerCustomContent": null, + "enableSearch": true, + "enableOpenApi": true, + "enableHomeCustom": false, + "homeCustomLocation": null, + "enableGroup": true, + "enableResponseCode": true + } + } +} diff --git a/tests/fixtures/OpenAPI_V3.json b/tests/fixtures/OpenAPI_V3.json new file mode 100644 index 0000000..1b4e571 --- /dev/null +++ b/tests/fixtures/OpenAPI_V3.json @@ -0,0 +1,245 @@ +{ + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError" + } + }, + "securitySchemes": { + "HTTPBearer": { + "type": "http", + "scheme": "bearer" + } + } + }, + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "openapi": "3.1.0", + "paths": { + "/course-segments/": { + "get": { + "summary": "Read Course Segments", + "operationId": "read_course_segments_course_segments__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/problem-set/{problem_set_id}/{question_number}": { + "get": { + "summary": "Read Problem Set Item", + "operationId": "read_problem_set_item_problem_set__problem_set_id___question_number__get", + "parameters": [ + { + "name": "problem_set_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Problem Set Id" + } + }, + { + "name": "question_number", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Question Number" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/problem-set/random/{problem_set_id}/{n_items}": { + "get": { + "summary": "Read Random Problem Set Items", + "operationId": "read_random_problem_set_items_problem_set_random__problem_set_id___n_items__get", + "parameters": [ + { + "name": "problem_set_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Problem Set Id" + } + }, + { + "name": "n_items", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "N Items" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/problem-set/range/{problem_set_id}/{start}/{end}": { + "get": { + "summary": "Read Range Of Problem Set Items", + "operationId": "read_range_of_problem_set_items_problem_set_range__problem_set_id___start___end__get", + "parameters": [ + { + "name": "problem_set_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Problem Set Id" + } + }, + { + "name": "start", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "Start" + } + }, + { + "name": "end", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "title": "End" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/user/": { + "get": { + "summary": "Read User Id", + "operationId": "read_user_id_user__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + }, + "security": [ + { + "HTTPBearer": [] + } + ] + } + } + } +} diff --git a/tests/fixtures/OpenAPI_V3_0_2.json b/tests/fixtures/OpenAPI_V3_0_2.json new file mode 100644 index 0000000..aa7ac42 --- /dev/null +++ b/tests/fixtures/OpenAPI_V3_0_2.json @@ -0,0 +1,215 @@ +{ + "components": { + "schemas": { + "DocumentMetadata": { + "title": "DocumentMetadata", + "required": ["source", "page_number", "author"], + "type": "object", + "properties": { + "source": { + "title": "Source", + "type": "string" + }, + "page_number": { + "title": "Page Number", + "type": "integer" + }, + "author": { + "title": "Author", + "type": "string" + } + } + }, + "FileResponse": { + "title": "FileResponse", + "required": ["docId"], + "type": "object", + "properties": { + "docId": { + "title": "Docid", + "type": "string" + }, + "error": { + "title": "Error", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "InputData": { + "title": "InputData", + "required": ["doc_id", "query"], + "type": "object", + "properties": { + "doc_id": { + "title": "Doc Id", + "type": "string" + }, + "query": { + "title": "Query", + "type": "string" + } + } + }, + "ResponseModel": { + "title": "ResponseModel", + "required": ["results"], + "type": "object", + "properties": { + "results": { + "title": "Results", + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchResult" + } + } + } + }, + "SearchResult": { + "title": "SearchResult", + "required": ["doc_id", "text", "metadata"], + "type": "object", + "properties": { + "doc_id": { + "title": "Doc Id", + "type": "string" + }, + "text": { + "title": "Text", + "type": "string" + }, + "metadata": { + "$ref": "#/components/schemas/DocumentMetadata" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + } + } + }, + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "openapi": "3.0.2", + "paths": { + "/api/download_pdf": { + "post": { + "summary": "Download Pdf", + "description": "Download a PDF file from a URL and save it to the vector database.", + "operationId": "download_pdf_api_download_pdf_post", + "parameters": [ + { + "required": true, + "schema": { + "title": "Url", + "type": "string" + }, + "name": "url", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FileResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/query": { + "post": { + "summary": "Perform Query", + "description": "Perform a query on a document.", + "operationId": "perform_query_query_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InputData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + } +} diff --git a/tests/openapi.test.ts b/tests/openapi.test.ts new file mode 100644 index 0000000..c964729 --- /dev/null +++ b/tests/openapi.test.ts @@ -0,0 +1,197 @@ +import { OpenAPIConvertor } from '@lobehub/chat-plugin-sdk'; +import { describe, expect, it } from 'vitest'; + +import OpenAPI_Auth_API_Key from './fixtures/OpenAPI_Auth_API_Key.json'; +import OpenAPIV2 from './fixtures/OpenAPI_V2.json'; +import openAPIV3 from './fixtures/OpenAPI_V3.json'; +import OpenAPI_V3_0_2 from './fixtures/OpenAPI_V3_0_2.json'; + +describe('OpenAPIConvertor', () => { + describe('convertOpenAPIToPluginSchema', () => { + it('can convert OpenAPI v3.1 to lobe apis', async () => { + const convertor = new OpenAPIConvertor(openAPIV3); + const plugins = await convertor.convertOpenAPIToPluginSchema(); + + expect(plugins).toMatchSnapshot(); + }); + + it('can convert OpenAPI v2 MJ openAPI', async () => { + const convertor = new OpenAPIConvertor(OpenAPIV2); + const plugins = await convertor.convertOpenAPIToPluginSchema(); + + expect(plugins).toMatchSnapshot(); + }); + + it('can convert OpenAPI v3.0.2 openAPI', async () => { + const convertor = new OpenAPIConvertor(OpenAPI_V3_0_2); + const plugins = await convertor.convertOpenAPIToPluginSchema(); + + expect(plugins).toEqual([ + { + description: 'Download Pdf', + name: 'download_pdf_api_download_pdf_post', + parameters: { + properties: { + url: { + title: 'Url', + type: 'string', + }, + }, + required: ['url'], + type: 'object', + }, + }, + { + description: 'Perform Query', + name: 'perform_query_query_post', + parameters: { + properties: { + doc_id: { + title: 'Doc Id', + type: 'string', + }, + query: { + title: 'Query', + type: 'string', + }, + }, + required: ['doc_id', 'query'], + type: 'object', + }, + }, + ]); + }); + }); + + describe('convertAuthToSettingsSchema', () => { + it('do not need has settings', async () => { + const convertor = new OpenAPIConvertor(OpenAPIV2); + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: {}, + type: 'object', + }); + }); + + it('can convert OpenAPI Bear key to settings', async () => { + const convertor = new OpenAPIConvertor(openAPIV3); + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: { + HTTPBearer: { + description: 'HTTPBearer Bearer token', + format: 'password', + title: 'HTTPBearer', + type: 'string', + }, + }, + required: ['HTTPBearer'], + type: 'object', + }); + }); + + it('can convert OpenAPI Auth API key to settings', async () => { + const convertor = new OpenAPIConvertor(OpenAPI_Auth_API_Key); + + const settings = await convertor.convertAuthToSettingsSchema({ + properties: { + abc: {}, + }, + required: ['abc', 'apiKeyAuth'], + type: 'object', + }); + + expect(settings).toEqual({ + properties: { + abc: {}, + apiKeyAuth: { + description: 'apiKeyAuth API Key', + format: 'password', + title: 'X-OpenAPIHub-Key', + type: 'string', + }, + }, + required: ['abc', 'apiKeyAuth'], + type: 'object', + }); + }); + + it('can convert OpenAPI Basic Auth to settings', async () => { + // 假设的 OpenAPI 配置示例,需要根据实际情况调整 + const OpenAPI_Basic_Auth = { + components: { + securitySchemes: { basicAuth: { scheme: 'basic', type: 'http' } }, + }, + }; + const convertor = new OpenAPIConvertor(OpenAPI_Basic_Auth); + + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: { + basicAuth: { + description: 'Basic authentication credentials', + format: 'password', + type: 'string', + }, + }, + required: ['basicAuth'], + type: 'object', + }); + }); + + it('can convert OpenAPI OAuth2 to settings', async () => { + const OpenAPI_OAuth2 = { + components: { securitySchemes: { oauth2: { type: 'oauth2' } } }, + }; + const convertor = new OpenAPIConvertor(OpenAPI_OAuth2); + + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: { + oauth2_accessToken: { + description: 'Access token for OAuth2', + format: 'password', + type: 'string', + }, + oauth2_clientId: { + description: 'Client ID for OAuth2', + type: 'string', + }, + oauth2_clientSecret: { + description: 'Client Secret for OAuth2', + format: 'password', + type: 'string', + }, + }, + required: ['oauth2_clientId', 'oauth2_clientSecret', 'oauth2_accessToken'], + type: 'object', + }); + }); + + it('should handle cases where securitySchemes is undefined', async () => { + const convertor = new OpenAPIConvertor({}); + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: {}, + type: 'object', + }); + }); + + it('should handle cases where components is undefined', async () => { + const openApiWithoutComponents = { paths: {} }; + + const convertor = new OpenAPIConvertor(openApiWithoutComponents); + const plugins = await convertor.convertAuthToSettingsSchema(); + + expect(plugins).toEqual({ + properties: {}, + type: 'object', + }); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index b1fda89..9393921 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,8 @@ "skipDefaultLibCheck": true, "jsx": "react-jsx", "baseUrl": ".", + "target": "ESNext", + "lib": ["ESNext", "dom"], "allowSyntheticDefaultImports": true, "declaration": true, "moduleResolution": "node", @@ -17,8 +19,8 @@ "@@/*": ["example/.dumi/tmp/*"], "@/*": ["src/*"], "dumi/theme/*": ["example/.dumi/tmp/dumi/theme/*"], - "chat-plugin-sdk": ["src"], - "chat-plugin-sdk/*": ["src/*", "*"] + "@lobehub/chat-plugin-sdk": ["src"], + "@lobehub/chat-plugin-sdk/*": ["src/*", "*"] }, "types": ["vitest/globals"] },