fix openapi 3 test doc and tests

This commit is contained in:
Maurits Lourens 2022-03-03 15:19:36 +01:00
parent c1d2a4680a
commit 090da34a99
5 changed files with 225 additions and 225 deletions

View File

@ -23,7 +23,7 @@ export abstract class ImportSource {
name: string, name: string,
method: string, method: string,
path: string, path: string,
url: URL, url: URL | null,
queryString: string, queryString: string,
headers: object = {}, headers: object = {},
parameters: QueryParameter[] = [], parameters: QueryParameter[] = [],
@ -34,7 +34,9 @@ export abstract class ImportSource {
const transformer = "return data" const transformer = "return data"
const schema = {} const schema = {}
path = this.processPath(path) path = this.processPath(path)
if (url) {
path = `${url.origin}/${path}` path = `${url.origin}/${path}`
}
queryString = this.processQuery(queryString) queryString = this.processQuery(queryString)
const requestBody = JSON.stringify(body, null, 2) const requestBody = JSON.stringify(body, null, 2)

View File

@ -3,14 +3,7 @@ import { Query, QueryParameter } from "../../../../../definitions/datasource"
import { OpenAPIV3 } from "openapi-types" import { OpenAPIV3 } from "openapi-types"
import { OpenAPISource } from "./base/openapi" import { OpenAPISource } from "./base/openapi"
import { URL } from "url" import { URL } from "url"
import {
getGlobalDB,
getTenantId,
isMultiTenant,
} from "@budibase/backend-core/tenancy"
import { getScopedConfig } from "@budibase/backend-core/db"
const jsonMimeType = "application/json"
const parameterNotRef = ( const parameterNotRef = (
param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject
): param is OpenAPIV3.ParameterObject => { ): param is OpenAPIV3.ParameterObject => {
@ -25,6 +18,13 @@ const requestBodyNotRef = (
return param !== undefined return param !== undefined
} }
const schemaNotRef = (
param: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined
): param is OpenAPIV3.SchemaObject => {
// all refs are deferenced by parser library
return param !== undefined
}
const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => { const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => {
return document.openapi.includes("3") return document.openapi.includes("3")
} }
@ -49,9 +49,25 @@ const getRequestBody = (operation: OpenAPIV3.OperationObject) => {
if (requestBodyNotRef(operation.requestBody)) { if (requestBodyNotRef(operation.requestBody)) {
const request: OpenAPIV3.RequestBodyObject = const request: OpenAPIV3.RequestBodyObject =
operation.requestBody as OpenAPIV3.RequestBodyObject operation.requestBody as OpenAPIV3.RequestBodyObject
return request.content[jsonMimeType]?.example const supportedMimeTypes = getMimeTypes(operation)
return supportedMimeTypes.length > 0 &&
schemaNotRef(request.content[supportedMimeTypes[0]].schema)
? (
request.content[supportedMimeTypes[0]]
.schema as OpenAPIV3.SchemaObject
).example
: undefined
} }
return null return undefined
}
const getMimeTypes = (operation: OpenAPIV3.OperationObject): string[] => {
if (requestBodyNotRef(operation.requestBody)) {
const request: OpenAPIV3.RequestBodyObject =
operation.requestBody as OpenAPIV3.RequestBodyObject
return Object.keys(request.content)
}
return []
} }
/** /**
@ -75,34 +91,6 @@ export class OpenAPI3 extends OpenAPISource {
} }
} }
getPlatformUrl = async (): Promise<string> => {
const db = getGlobalDB()
const publicConfig = await getScopedConfig(db, {
type: "settings",
})
let url = publicConfig.platformUrl
if (isMultiTenant()) {
url += `/${getTenantId()}`
}
return url
}
getUrl = async (): Promise<URL> => {
const platformUrl = await this.getPlatformUrl()
let url = this.document.servers ? this.document.servers[0].url : null
if (url) {
// check if url is relative or absolute
if (url.includes("http") || url.includes("https")) {
return new URL(url)
}
return new URL(`${platformUrl}${url}`)
}
// if the specification doesn't contain a servers object, return the PLATFORM_URM environment variable
return new URL(platformUrl)
}
getInfo = async (): Promise<ImportInfo> => { getInfo = async (): Promise<ImportInfo> => {
const name = this.document.info.title || "Swagger Import" const name = this.document.info.title || "Swagger Import"
return { return {
@ -111,7 +99,9 @@ export class OpenAPI3 extends OpenAPISource {
} }
getQueries = async (datasourceId: string): Promise<Query[]> => { getQueries = async (datasourceId: string): Promise<Query[]> => {
const url: URL = await this.getUrl() const url: URL | null = this.document.servers
? new URL(this.document.servers[0].url)
: null
const queries: Query[] = [] const queries: Query[] = []
for (let [path, pathItemObject] of Object.entries(this.document.paths)) { for (let [path, pathItemObject] of Object.entries(this.document.paths)) {
@ -138,6 +128,11 @@ export class OpenAPI3 extends OpenAPISource {
const headers: any = {} const headers: any = {}
let requestBody = getRequestBody(operation) let requestBody = getRequestBody(operation)
const parameters: QueryParameter[] = [] const parameters: QueryParameter[] = []
const mimeTypes = getMimeTypes(operation)
if (mimeTypes.length > 0) {
headers["Content-Type"] = mimeTypes[0]
}
// combine the path parameters with the operation parameters // combine the path parameters with the operation parameters
const operationParams = operation.parameters || [] const operationParams = operation.parameters || []

View File

@ -5,6 +5,11 @@
"version": "1.0.0", "version": "1.0.0",
"title": "CRUD" "title": "CRUD"
}, },
"servers": [
{
"url": "http://example.com"
}
],
"tags": [ "tags": [
{ {
"name": "entity" "name": "entity"
@ -17,50 +22,59 @@
"entity" "entity"
], ],
"operationId": "createEntity", "operationId": "createEntity",
"parameters": [ "requestBody": {
{ "content": {
"$ref": "#/components/schemas/CreateEntityParameter" "application/json": {
"schema": {
"$ref": "#/components/schemas/CreateEntity"
} }
], }
}
},
"responses": { "responses": {
"200": { "200": {
"description": "successful operation", "description": "successful operation",
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/Entity" "$ref": "#/components/schemas/Entity"
} }
} }
} }
}
}
}, },
"get": { "get": {
"tags": [ "tags": [
"entity" "entity"
], ],
"operationId": "getEntities", "operationId": "getEntities",
"produces": [
"application/json"
],
"parameters": [ "parameters": [
{ {
"$ref": "#/components/schemas/PageParameter" "$ref": "#/components/parameters/PageParameter"
}, },
{ {
"$ref": "#/components/schemas/SizeParameter" "$ref": "#/components/parameters/SizeParameter"
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "successful operation", "description": "successful operation",
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/Entities" "$ref": "#/components/schemas/Entities"
} }
} }
} }
} }
}
}
}, },
"/entities/{entityId}": { "/entities/{entityId}": {
"parameters": [ "parameters": [
{ {
"$ref": "#/components/schemas/EntityIdParameter" "$ref": "#/components/parameters/EntityIdParameter"
} }
], ],
"get": { "get": {
@ -68,55 +82,72 @@
"entity" "entity"
], ],
"operationId": "getEntity", "operationId": "getEntity",
"produces": [
"application/json"
],
"responses": { "responses": {
"200": { "200": {
"description": "successful operation", "description": "successful operation",
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/Entity" "$ref": "#/components/schemas/Entity"
} }
} }
} }
}
}
}, },
"put": { "put": {
"tags": [ "tags": [
"entity" "entity"
], ],
"operationId": "updateEntity", "operationId": "updateEntity",
"parameters": [ "requestBody": {
{ "content": {
"$ref": "#/components/schemas/EntityParameter" "application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
} }
], }
}
},
"responses": { "responses": {
"200": { "200": {
"description": "successful operation", "description": "successful operation",
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/Entity" "$ref": "#/components/schemas/Entity"
} }
} }
} }
}
}
}, },
"patch": { "patch": {
"tags": [ "tags": [
"entity" "entity"
], ],
"operationId": "patchEntity", "operationId": "patchEntity",
"parameters": [ "requestBody": {
{ "content": {
"$ref": "#/components/schemas/EntityParameter" "application/json": {
"schema": {
"$ref": "#/components/schemas/Entity"
} }
], }
}
},
"responses": { "responses": {
"200": { "200": {
"description": "successful operation", "description": "successful operation",
"content": {
"application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/Entity" "$ref": "#/components/schemas/Entity"
} }
} }
} }
}
}
}, },
"delete": { "delete": {
"tags": [ "tags": [
@ -124,10 +155,7 @@
], ],
"parameters": [ "parameters": [
{ {
"type": "string", "$ref": "#/components/parameters/APIKeyParameter"
"name": "x-api-key",
"in": "header",
"required": false
} }
], ],
"operationId": "deleteEntity", "operationId": "deleteEntity",
@ -140,50 +168,44 @@
} }
}, },
"components": { "components": {
"schemas": { "parameters": {
"EntityIdParameter": { "EntityIdParameter": {
"schema": {
"type": "integer", "type": "integer",
"format": "int64", "format": "int64"
},
"name": "entityId", "name": "entityId",
"in": "path", "in": "path",
"required": true "required": true
}, },
"CreateEntityParameter": {
"name": "entity",
"in": "body",
"required": true,
"schema": {
"$ref": "#/components/schemas/CreateEntity"
}
},
"EntityParameter": {
"name": "entity",
"in": "body",
"required": true,
"schema": {
"$ref": "#/components/schemas/Entity"
}
},
"PageParameter": { "PageParameter": {
"schema": {
"type": "integer", "type": "integer",
"format": "int32", "format": "int32"
},
"name": "page", "name": "page",
"in": "query", "in": "query",
"required": false "required": false
}, },
"SizeParameter": { "SizeParameter": {
"schema": {
"type": "integer", "type": "integer",
"format": "int32", "format": "int32"
},
"name": "size", "name": "size",
"in": "query", "in": "query",
"required": false "required": false
}, },
"APIKeyParameter": { "APIKeyParameter": {
"type": "string", "schema": {
"type": "string"
},
"name": "x-api-key", "name": "x-api-key",
"in": "header", "in": "header",
"required": false "required": false
}
}, },
"schemas": {
"CreateEntity": { "CreateEntity": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1,10 +1,11 @@
--- ---
openapi: "3.0.2" openapi: 3.0.2
info: info:
description: A basic swagger file description: A basic swagger file
version: 1.0.0 version: 1.0.0
title: CRUD title: CRUD
host: example.com servers:
- url: http://example.com
tags: tags:
- name: entity - name: entity
paths: paths:
@ -13,121 +14,117 @@ paths:
tags: tags:
- entity - entity
operationId: createEntity operationId: createEntity
produces: requestBody:
- application/json content:
parameters: application/json:
- "$ref": "#/parameters/CreateEntity"
responses:
"200":
description: successful operation
schema: schema:
"$ref": "#/definitions/Entity" "$ref": "#/components/schemas/CreateEntity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
get: get:
tags: tags:
- entity - entity
operationId: getEntities operationId: getEntities
produces:
- application/json
parameters: parameters:
- "$ref": "#/parameters/Page" - "$ref": "#/components/parameters/PageParameter"
- "$ref": "#/parameters/Size" - "$ref": "#/components/parameters/SizeParameter"
responses: responses:
"200": '200':
description: successful operation description: successful operation
content:
application/json:
schema: schema:
"$ref": "#/definitions/Entities" "$ref": "#/components/schemas/Entities"
"/entities/{entityId}": "/entities/{entityId}":
parameters: parameters:
- "$ref": "#/parameters/EntityId" - "$ref": "#/components/parameters/EntityIdParameter"
get: get:
tags: tags:
- entity - entity
operationId: getEntity operationId: getEntity
produces:
- application/json
responses: responses:
"200": '200':
description: successful operation description: successful operation
content:
application/json:
schema: schema:
"$ref": "#/definitions/Entity" "$ref": "#/components/schemas/Entity"
put: put:
tags: tags:
- entity - entity
operationId: updateEntity operationId: updateEntity
consumes: requestBody:
- application/json content:
produces: application/json:
- application/json
parameters:
- "$ref": "#/parameters/Entity"
responses:
"200":
description: successful operation
schema: schema:
"$ref": "#/definitions/Entity" "$ref": "#/components/schemas/Entity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
patch: patch:
tags: tags:
- entity - entity
operationId: patchEntity operationId: patchEntity
consumes: requestBody:
- application/json content:
produces: application/json:
- application/json
parameters:
- "$ref": "#/parameters/Entity"
responses:
"200":
description: successful operation
schema: schema:
"$ref": "#/definitions/Entity" "$ref": "#/components/schemas/Entity"
responses:
'200':
description: successful operation
content:
application/json:
schema:
"$ref": "#/components/schemas/Entity"
delete: delete:
tags: tags:
- entity - entity
parameters: parameters:
- "$ref": "#/parameters/APIKey" - "$ref": "#/components/parameters/APIKeyParameter"
operationId: deleteEntity operationId: deleteEntity
produces:
- application/json
responses: responses:
"204": '204':
description: successful operation description: successful operation
parameters: components:
EntityId: parameters:
EntityIdParameter:
schema:
type: integer type: integer
format: int64 format: int64
name: entityId name: entityId
in: path in: path
required: true required: true
CreateEntity: PageParameter:
name: entity
in: body
required: true
schema: schema:
"$ref": "#/definitions/CreateEntity"
Entity:
name: entity
in: body
required: true
schema:
"$ref": "#/definitions/Entity"
Page:
type: integer type: integer
format: int32 format: int32
name: page name: page
in: query in: query
required: false required: false
Size: SizeParameter:
schema:
type: integer type: integer
format: int32 format: int32
name: size name: size
in: query in: query
required: false required: false
APIKey: APIKeyParameter:
schema:
type: string type: string
name: x-api-key name: x-api-key
in: header in: header
required: false required: false
definitions: schemas:
CreateEntity: CreateEntity:
type: object type: object
properties: properties:
@ -145,7 +142,7 @@ definitions:
id: id:
type: integer type: integer
format: int64 format: int64
- "$ref": "#/definitions/CreateEntity" - "$ref": "#/components/schemas/CreateEntity"
example: example:
id: 1 id: 1
name: name name: name
@ -153,4 +150,4 @@ definitions:
Entities: Entities:
type: array type: array
items: items:
"$ref": "#/definitions/Entity" "$ref": "#/components/schemas/Entity"

View File

@ -1,21 +1,6 @@
const fs = require("fs") const fs = require("fs")
const path = require("path") const path = require("path")
const getGlobalDB = jest.fn().mockReturnValue("db")
const getTenantId = jest.fn().mockReturnValue("1")
const isMultiTenant = jest.fn().mockReturnValue(true)
const getScopedConfig = jest
.fn()
.mockResolvedValue({ platformUrl: "somePlatform" })
jest.mock("@budibase/backend-core/db", () => ({
getScopedConfig,
}))
jest.mock("@budibase/backend-core/tenancy", () => ({
getTenantId,
getGlobalDB,
isMultiTenant,
}))
const { OpenAPI3 } = require("../../openapi3") const { OpenAPI3 } = require("../../openapi3")
const getData = (file, extension) => { const getData = (file, extension) => {
@ -48,10 +33,9 @@ describe("OpenAPI3 Import", () => {
}) })
const runTests = async (filename, test, assertions) => { const runTests = async (filename, test, assertions) => {
/*for (let extension of ["json", "yaml"]) { for (let extension of ["json", "yaml"]) {
await test(filename, extension, assertions) await test(filename, extension, assertions)
}*/ }
await test(filename, "json", assertions)
} }
const testImportInfo = async (file, extension) => { const testImportInfo = async (file, extension) => {