mirror of https://github.com/Budibase/budibase.git
57 changed files with 3991 additions and 1019 deletions
@ -0,0 +1,135 @@ |
|||
<script> |
|||
import { goto } from "@roxi/routify" |
|||
import { |
|||
ModalContent, |
|||
notifications, |
|||
Body, |
|||
Layout, |
|||
Tabs, |
|||
Tab, |
|||
Input, |
|||
Heading, |
|||
TextArea, |
|||
Dropzone, |
|||
} from "@budibase/bbui" |
|||
import analytics, { Events } from "analytics" |
|||
import { datasources, queries } from "stores/backend" |
|||
import { writable } from "svelte/store" |
|||
|
|||
export let navigateDatasource = false |
|||
export let datasourceId |
|||
export let createDatasource = false |
|||
export let onCancel |
|||
|
|||
const data = writable({ |
|||
url: "", |
|||
raw: "", |
|||
file: undefined, |
|||
}) |
|||
|
|||
let lastTouched = "url" |
|||
|
|||
const getData = async () => { |
|||
let dataString |
|||
|
|||
// parse the file into memory and send as string |
|||
if (lastTouched === "file") { |
|||
dataString = await $data.file.text() |
|||
} else if (lastTouched === "url") { |
|||
const response = await fetch($data.url) |
|||
dataString = await response.text() |
|||
} else if (lastTouched === "raw") { |
|||
dataString = $data.raw |
|||
} |
|||
|
|||
return dataString |
|||
} |
|||
|
|||
async function importQueries() { |
|||
try { |
|||
const dataString = await getData() |
|||
|
|||
if (!datasourceId && !createDatasource) { |
|||
throw new Error("No datasource id") |
|||
} |
|||
|
|||
const body = { |
|||
data: dataString, |
|||
datasourceId, |
|||
} |
|||
|
|||
const importResult = await queries.import(body) |
|||
if (!datasourceId) { |
|||
datasourceId = importResult.datasourceId |
|||
} |
|||
|
|||
// reload |
|||
await datasources.fetch() |
|||
await queries.fetch() |
|||
await datasources.select(datasourceId) |
|||
|
|||
if (navigateDatasource) { |
|||
$goto(`./datasource/${datasourceId}`) |
|||
} |
|||
|
|||
notifications.success(`Imported successfully.`) |
|||
analytics.captureEvent(Events.QUERIES.REST.IMPORTED, { |
|||
importType: lastTouched, |
|||
newDatasource: createDatasource, |
|||
}) |
|||
|
|||
return true |
|||
} catch (err) { |
|||
notifications.error(`Error importing: ${err}`) |
|||
return false |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
onConfirm={() => importQueries()} |
|||
{onCancel} |
|||
confirmText={"Import"} |
|||
cancelText="Back" |
|||
size="L" |
|||
> |
|||
<Layout noPadding> |
|||
<Heading size="S">Import</Heading> |
|||
<Body size="XS" |
|||
>Import your rest collection using one of the options below</Body |
|||
> |
|||
<Tabs selected="Link"> |
|||
<Tab title="Link"> |
|||
<Input |
|||
bind:value={$data.url} |
|||
on:change={() => (lastTouched = "url")} |
|||
label="Enter a URL" |
|||
placeholder="e.g. https://petstore.swagger.io/v2/swagger.json" |
|||
/> |
|||
</Tab> |
|||
<Tab title="File"> |
|||
<Dropzone |
|||
gallery={false} |
|||
value={$data.file ? [$data.file] : []} |
|||
on:change={e => { |
|||
$data.file = e.detail?.[0] |
|||
lastTouched = "file" |
|||
}} |
|||
fileTags={["OpenAPI 2.0", "Swagger 2.0", "cURL", "YAML", "JSON"]} |
|||
maximum={1} |
|||
/> |
|||
</Tab> |
|||
<Tab title="Raw Text"> |
|||
<TextArea |
|||
bind:value={$data.raw} |
|||
on:change={() => (lastTouched = "raw")} |
|||
label={"Paste raw text"} |
|||
placeholder={'e.g. curl --location --request GET "https://example.com"'} |
|||
/> |
|||
</Tab> |
|||
</Tabs> |
|||
</Layout> |
|||
</ModalContent> |
|||
|
|||
<style> |
|||
</style> |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Duplicates a name with respect to a collection of existing names |
|||
* e.g. |
|||
* name all names result |
|||
* ------ ----------- -------- |
|||
* ("foo") ["foo"] "foo (1)" |
|||
* ("foo") ["foo", "foo (1)"] "foo (2)" |
|||
* ("foo (1)") ["foo", "foo (1)"] "foo (2)" |
|||
* ("foo") ["foo", "foo (2)"] "foo (1)" |
|||
* |
|||
* Repl |
|||
*/ |
|||
export const duplicateName = (name, allNames) => { |
|||
const baseName = name.split(" (")[0] |
|||
const isDuplicate = new RegExp(`${baseName}\\s\\((\\d+)\\)$`) |
|||
|
|||
// get the sequence from matched names
|
|||
const sequence = [] |
|||
allNames.filter(n => { |
|||
if (n === baseName) { |
|||
return true |
|||
} |
|||
const match = n.match(isDuplicate) |
|||
if (match) { |
|||
sequence.push(parseInt(match[1])) |
|||
return true |
|||
} |
|||
return false |
|||
}) |
|||
sequence.sort((a, b) => a - b) |
|||
|
|||
// get the next number in the sequence
|
|||
let number |
|||
if (sequence.length === 0) { |
|||
number = 1 |
|||
} else { |
|||
// get the next number in the sequence
|
|||
for (let i = 0; i < sequence.length; i++) { |
|||
if (sequence[i] !== i + 1) { |
|||
number = i + 1 |
|||
break |
|||
} |
|||
} |
|||
if (!number) { |
|||
number = sequence.length + 1 |
|||
} |
|||
} |
|||
|
|||
return `${baseName} (${number})` |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
const { duplicateName } = require("../duplicate") |
|||
|
|||
describe("duplicate", () => { |
|||
|
|||
describe("duplicates a name ", () => { |
|||
it("with a single existing", async () => { |
|||
const names = ["foo"] |
|||
const name = "foo" |
|||
|
|||
const duplicate = duplicateName(name, names) |
|||
|
|||
expect(duplicate).toBe("foo (1)") |
|||
}) |
|||
|
|||
it("with multiple existing", async () => { |
|||
const names = ["foo", "foo (1)", "foo (2)"] |
|||
const name = "foo" |
|||
|
|||
const duplicate = duplicateName(name, names) |
|||
|
|||
expect(duplicate).toBe("foo (3)") |
|||
}) |
|||
|
|||
it("with mixed multiple existing", async () => { |
|||
const names = ["foo", "foo (1)", "foo (2)", "bar", "bar (1)", "bar (2)"] |
|||
const name = "foo" |
|||
|
|||
const duplicate = duplicateName(name, names) |
|||
|
|||
expect(duplicate).toBe("foo (3)") |
|||
}) |
|||
|
|||
it("with incomplete sequence", async () => { |
|||
const names = ["foo", "foo (2)", "foo (3)"] |
|||
const name = "foo" |
|||
|
|||
const duplicate = duplicateName(name, names) |
|||
|
|||
expect(duplicate).toBe("foo (1)") |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,85 @@ |
|||
import CouchDB from "../../../../db" |
|||
import { queryValidation } from "../validation" |
|||
import { generateQueryID } from "../../../../db/utils" |
|||
import { ImportInfo, ImportSource } from "./sources/base" |
|||
import { OpenAPI2 } from "./sources/openapi2" |
|||
import { Query } from './../../../../definitions/common'; |
|||
import { Curl } from "./sources/curl" |
|||
interface ImportResult { |
|||
errorQueries: Query[] |
|||
queries: Query[] |
|||
} |
|||
|
|||
export class RestImporter { |
|||
data: string |
|||
sources: ImportSource[] |
|||
source!: ImportSource |
|||
|
|||
constructor(data: string) { |
|||
this.data = data |
|||
this.sources = [new OpenAPI2(), new Curl()] |
|||
} |
|||
|
|||
init = async () => { |
|||
for (let source of this.sources) { |
|||
if (await source.isSupported(this.data)) { |
|||
this.source = source |
|||
break |
|||
} |
|||
} |
|||
} |
|||
|
|||
getInfo = async (): Promise<ImportInfo> => { |
|||
return this.source.getInfo() |
|||
} |
|||
|
|||
importQueries = async ( |
|||
appId: string, |
|||
datasourceId: string |
|||
): Promise<ImportResult> => { |
|||
// constuct the queries
|
|||
let queries = await this.source.getQueries(datasourceId) |
|||
|
|||
// validate queries
|
|||
const errorQueries: Query[] = [] |
|||
const schema = queryValidation() |
|||
queries = queries |
|||
.filter(query => { |
|||
const validation = schema.validate(query) |
|||
if (validation.error) { |
|||
errorQueries.push(query) |
|||
return false |
|||
} |
|||
return true |
|||
}) |
|||
.map(query => { |
|||
query._id = generateQueryID(query.datasourceId) |
|||
return query |
|||
}) |
|||
|
|||
// persist queries
|
|||
const db = new CouchDB(appId) |
|||
const response = await db.bulkDocs(queries) |
|||
|
|||
// create index to seperate queries and errors
|
|||
const queryIndex = queries.reduce((acc, query) => { |
|||
if (query._id) { |
|||
acc[query._id] = query |
|||
} |
|||
return acc |
|||
}, {} as { [key: string]: Query }) |
|||
|
|||
// check for failed writes
|
|||
response.forEach((query: any) => { |
|||
if (!query.ok) { |
|||
errorQueries.push(queryIndex[query.id]) |
|||
delete queryIndex[query.id] |
|||
} |
|||
}) |
|||
|
|||
return { |
|||
errorQueries, |
|||
queries: Object.values(queryIndex), |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
|
|||
import { Query, QueryParameter } from "../../../../../../definitions/common" |
|||
|
|||
export interface ImportInfo { |
|||
url: string |
|||
name: string |
|||
} |
|||
|
|||
enum MethodToVerb { |
|||
get = "read", |
|||
post = "create", |
|||
put = "update", |
|||
patch = "patch", |
|||
delete = "delete", |
|||
} |
|||
|
|||
export abstract class ImportSource { |
|||
abstract isSupported(data: string): Promise<boolean> |
|||
abstract getInfo(): Promise<ImportInfo> |
|||
abstract getQueries(datasourceId: string): Promise<Query[]> |
|||
|
|||
constructQuery = ( |
|||
datasourceId: string, |
|||
name: string, |
|||
method: string, |
|||
path: string, |
|||
queryString: string, |
|||
headers: object = {}, |
|||
parameters: QueryParameter[] = [], |
|||
body: object | undefined = undefined |
|||
): Query => { |
|||
const readable = true |
|||
const queryVerb = this.verbFromMethod(method) |
|||
const transformer = "return data" |
|||
const schema = {} |
|||
path = this.processPath(path) |
|||
queryString = this.processQuery(queryString) |
|||
const requestBody = JSON.stringify(body, null, 2) |
|||
|
|||
const query: Query = { |
|||
datasourceId, |
|||
name, |
|||
parameters, |
|||
fields: { |
|||
headers, |
|||
queryString, |
|||
path, |
|||
requestBody, |
|||
}, |
|||
transformer, |
|||
schema, |
|||
readable, |
|||
queryVerb, |
|||
} |
|||
|
|||
return query |
|||
} |
|||
|
|||
verbFromMethod = (method: string) => { |
|||
const verb = (<any>MethodToVerb)[method] |
|||
if (!verb) { |
|||
throw new Error(`Unsupported method: ${method}`) |
|||
} |
|||
return verb |
|||
} |
|||
|
|||
processPath = (path: string): string => { |
|||
if (path?.startsWith("/")) { |
|||
path = path.substring(1) |
|||
} |
|||
|
|||
// add extra braces around params for binding
|
|||
path = path.replace(/[{]/g, "{{") |
|||
path = path.replace(/[}]/g, "}}") |
|||
|
|||
return path |
|||
} |
|||
|
|||
processQuery = (queryString: string): string => { |
|||
if (queryString?.startsWith("?")) { |
|||
return queryString.substring(1) |
|||
} |
|||
|
|||
return queryString |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import { ImportSource } from "." |
|||
import SwaggerParser from "@apidevtools/swagger-parser" |
|||
import { OpenAPI } from "openapi-types" |
|||
const yaml = require("js-yaml") |
|||
|
|||
export abstract class OpenAPISource extends ImportSource { |
|||
parseData = async (data: string): Promise<OpenAPI.Document> => { |
|||
let json: OpenAPI.Document |
|||
try { |
|||
json = JSON.parse(data) |
|||
} catch (jsonErr) { |
|||
// couldn't parse json
|
|||
// try to convert yaml -> json
|
|||
try { |
|||
json = yaml.load(data) |
|||
} catch (yamlErr) { |
|||
// couldn't parse yaml
|
|||
throw new Error("Could not parse JSON or YAML") |
|||
} |
|||
} |
|||
|
|||
return SwaggerParser.validate(json, {}) |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
import { ImportSource, ImportInfo } from "./base" |
|||
import { Query } from "../../../../../definitions/common" |
|||
import { URL } from "url" |
|||
const curlconverter = require("curlconverter") |
|||
|
|||
const parseCurl = (data: string): any => { |
|||
const curlJson = curlconverter.toJsonString(data) |
|||
return JSON.parse(curlJson) |
|||
} |
|||
|
|||
/** |
|||
* The curl converter parses the request body into the key field of an object |
|||
* e.g. --d '{"key":"val"}' produces an object { "{"key":"val"}" : "" } |
|||
* This is not what we want, so we need to parse out the key from the object |
|||
*/ |
|||
const parseBody = (curl: any) => { |
|||
if (curl.data) { |
|||
const keys = Object.keys(curl.data) |
|||
if (keys.length) { |
|||
const key = keys[0] |
|||
try { |
|||
return JSON.parse(key) |
|||
} catch (e) { |
|||
// do nothing
|
|||
} |
|||
} |
|||
} |
|||
return undefined |
|||
} |
|||
|
|||
const parseCookie = (curl: any) => { |
|||
if (curl.cookies){ |
|||
return Object.entries(curl.cookies).reduce((acc, entry) => { |
|||
const [key, value] = entry |
|||
return acc + `${key}=${value}; ` |
|||
}, "") |
|||
} |
|||
|
|||
return null |
|||
} |
|||
|
|||
/** |
|||
* Curl |
|||
* https://curl.se/docs/manpage.html
|
|||
*/ |
|||
export class Curl extends ImportSource { |
|||
curl: any |
|||
|
|||
isSupported = async (data: string): Promise<boolean> => { |
|||
try { |
|||
const curl = parseCurl(data) |
|||
this.curl = curl |
|||
} catch (err) { |
|||
return false |
|||
} |
|||
return true |
|||
} |
|||
|
|||
getInfo = async (): Promise<ImportInfo> => { |
|||
const url = new URL(this.curl.url) |
|||
return { |
|||
url: url.origin, |
|||
name: url.hostname, |
|||
} |
|||
} |
|||
|
|||
getQueries = async (datasourceId: string): Promise<Query[]> => { |
|||
const url = new URL(this.curl.raw_url) |
|||
const name = url.pathname |
|||
const path = url.pathname |
|||
const method = this.curl.method |
|||
const queryString = url.search |
|||
const headers = this.curl.headers |
|||
const requestBody = parseBody(this.curl) |
|||
|
|||
const cookieHeader = parseCookie(this.curl) |
|||
if (cookieHeader) { |
|||
headers["Cookie"] = cookieHeader |
|||
} |
|||
|
|||
const query = this.constructQuery( |
|||
datasourceId, |
|||
name, |
|||
method, |
|||
path, |
|||
queryString, |
|||
headers, |
|||
[], |
|||
requestBody |
|||
) |
|||
|
|||
return [query] |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
import { ImportInfo } from "./base" |
|||
import { Query, QueryParameter } from "../../../../../definitions/common" |
|||
import { OpenAPIV2 } from "openapi-types" |
|||
import { OpenAPISource } from "./base/openapi" |
|||
|
|||
const parameterNotRef = ( |
|||
param: OpenAPIV2.Parameter | OpenAPIV2.ReferenceObject |
|||
): param is OpenAPIV2.Parameter => { |
|||
// all refs are deferenced by parser library
|
|||
return true |
|||
} |
|||
|
|||
const isOpenAPI2 = (document: any): document is OpenAPIV2.Document => { |
|||
if (document.swagger === "2.0") { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
const methods: string[] = Object.values(OpenAPIV2.HttpMethods) |
|||
|
|||
const isOperation = ( |
|||
key: string, |
|||
pathItem: any |
|||
): pathItem is OpenAPIV2.OperationObject => { |
|||
return methods.includes(key) |
|||
} |
|||
|
|||
const isParameter = ( |
|||
key: string, |
|||
pathItem: any |
|||
): pathItem is OpenAPIV2.Parameter => { |
|||
return !isOperation(key, pathItem) |
|||
} |
|||
|
|||
/** |
|||
* OpenAPI Version 2.0 - aka "Swagger" |
|||
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
|
|||
*/ |
|||
export class OpenAPI2 extends OpenAPISource { |
|||
document!: OpenAPIV2.Document |
|||
|
|||
isSupported = async (data: string): Promise<boolean> => { |
|||
try { |
|||
const document: any = await this.parseData(data) |
|||
if (isOpenAPI2(document)) { |
|||
this.document = document |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} catch (err) { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
getInfo = async (): Promise<ImportInfo> => { |
|||
const scheme = this.document.schemes?.includes("https") ? "https" : "http" |
|||
const basePath = this.document.basePath || "" |
|||
const host = this.document.host || "<host>" |
|||
const url = `${scheme}://${host}${basePath}` |
|||
const name = this.document.info.title || "Swagger Import" |
|||
|
|||
return { |
|||
url: url, |
|||
name: name, |
|||
} |
|||
} |
|||
|
|||
getQueries = async (datasourceId: string): Promise<Query[]> => { |
|||
const queries = [] |
|||
|
|||
for (let [path, pathItem] of Object.entries(this.document.paths)) { |
|||
// parameters that apply to every operation in the path
|
|||
let pathParams: OpenAPIV2.Parameter[] = [] |
|||
|
|||
for (let [key, opOrParams] of Object.entries(pathItem)) { |
|||
if (isParameter(key, opOrParams)) { |
|||
const pathParameters = opOrParams as OpenAPIV2.Parameter[] |
|||
pathParams.push(...pathParameters) |
|||
continue |
|||
} |
|||
// can not be a parameter, must be an operation
|
|||
const operation = opOrParams as OpenAPIV2.OperationObject |
|||
|
|||
const methodName = key |
|||
const name = operation.operationId || path |
|||
let queryString = "" |
|||
const headers: any = {} |
|||
let requestBody = undefined |
|||
const parameters: QueryParameter[] = [] |
|||
|
|||
if (operation.consumes) { |
|||
headers["Content-Type"] = operation.consumes[0] |
|||
} |
|||
|
|||
// combine the path parameters with the operation parameters
|
|||
const operationParams = operation.parameters || [] |
|||
const allParams = [...pathParams, ...operationParams] |
|||
|
|||
for (let param of allParams) { |
|||
if (parameterNotRef(param)) { |
|||
switch (param.in) { |
|||
case "query": |
|||
let prefix = "" |
|||
if (queryString) { |
|||
prefix = "&" |
|||
} |
|||
queryString = `${queryString}${prefix}${param.name}={{${param.name}}}` |
|||
break |
|||
case "header": |
|||
headers[param.name] = `{{${param.name}}}` |
|||
break |
|||
case "path": |
|||
// do nothing: param is already in the path
|
|||
break |
|||
case "formData": |
|||
// future enhancement
|
|||
break |
|||
case "body": |
|||
// set the request body to the example provided
|
|||
// future enhancement: generate an example from the schema
|
|||
let bodyParam: OpenAPIV2.InBodyParameterObject = |
|||
param as OpenAPIV2.InBodyParameterObject |
|||
if (param.schema.example) { |
|||
const schema = bodyParam.schema as OpenAPIV2.SchemaObject |
|||
requestBody = schema.example |
|||
} |
|||
break |
|||
} |
|||
|
|||
// add the parameter if it can be bound in our config
|
|||
if (["query", "header", "path"].includes(param.in)) { |
|||
parameters.push({ |
|||
name: param.name, |
|||
default: param.default || "", |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
|
|||
const query = this.constructQuery( |
|||
datasourceId, |
|||
name, |
|||
methodName, |
|||
path, |
|||
queryString, |
|||
headers, |
|||
parameters, |
|||
requestBody |
|||
) |
|||
queries.push(query) |
|||
} |
|||
} |
|||
|
|||
return queries |
|||
} |
|||
} |
|||
@ -0,0 +1,104 @@ |
|||
const { Curl } = require("../../curl") |
|||
const fs = require("fs") |
|||
const path = require('path') |
|||
|
|||
const getData = (file) => { |
|||
return fs.readFileSync(path.join(__dirname, `./data/${file}.txt`), "utf8") |
|||
} |
|||
|
|||
describe("Curl Import", () => { |
|||
let curl |
|||
|
|||
beforeEach(() => { |
|||
curl = new Curl() |
|||
}) |
|||
|
|||
it("validates unsupported data", async () => { |
|||
let data |
|||
let supported |
|||
|
|||
// JSON
|
|||
data = "{}" |
|||
supported = await curl.isSupported(data) |
|||
expect(supported).toBe(false) |
|||
|
|||
// Empty
|
|||
data = "" |
|||
supported = await curl.isSupported(data) |
|||
expect(supported).toBe(false) |
|||
}) |
|||
|
|||
const init = async (file) => { |
|||
await curl.isSupported(getData(file)) |
|||
} |
|||
|
|||
it("returns import info", async () => { |
|||
await init("get") |
|||
const info = await curl.getInfo() |
|||
expect(info.url).toBe("http://example.com") |
|||
expect(info.name).toBe("example.com") |
|||
}) |
|||
|
|||
describe("Returns queries", () => { |
|||
|
|||
const getQueries = async (file) => { |
|||
await init(file) |
|||
const queries = await curl.getQueries() |
|||
expect(queries.length).toBe(1) |
|||
return queries |
|||
} |
|||
|
|||
const testVerb = async (file, verb) => { |
|||
const queries = await getQueries(file) |
|||
expect(queries[0].queryVerb).toBe(verb) |
|||
} |
|||
|
|||
it("populates verb", async () => { |
|||
await testVerb("get", "read") |
|||
await testVerb("post", "create") |
|||
await testVerb("put", "update") |
|||
await testVerb("delete", "delete") |
|||
await testVerb("patch", "patch") |
|||
}) |
|||
|
|||
const testPath = async (file, urlPath) => { |
|||
const queries = await getQueries(file) |
|||
expect(queries[0].fields.path).toBe(urlPath) |
|||
} |
|||
|
|||
it("populates path", async () => { |
|||
await testPath("get", "") |
|||
await testPath("path", "paths/abc") |
|||
}) |
|||
|
|||
const testHeaders = async (file, headers) => { |
|||
const queries = await getQueries(file) |
|||
expect(queries[0].fields.headers).toStrictEqual(headers) |
|||
} |
|||
|
|||
it("populates headers", async () => { |
|||
await testHeaders("get", {}) |
|||
await testHeaders("headers", { "x-bb-header-1" : "123", "x-bb-header-2" : "456"} ) |
|||
}) |
|||
|
|||
const testQuery = async (file, queryString) => { |
|||
const queries = await getQueries(file) |
|||
expect(queries[0].fields.queryString).toBe(queryString) |
|||
} |
|||
it("populates query", async () => { |
|||
await testQuery("get", "") |
|||
await testQuery("query", "q1=v1&q1=v2") |
|||
}) |
|||
|
|||
const testBody = async (file, body) => { |
|||
const queries = await getQueries(file) |
|||
expect(queries[0].fields.requestBody).toStrictEqual(JSON.stringify(body, null, 2)) |
|||
} |
|||
|
|||
it("populates body", async () => { |
|||
await testBody("get", undefined) |
|||
await testBody("post", { "key" : "val" }) |
|||
await testBody("empty-body", {}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1 @@ |
|||
curl -X DELETE 'http://example.com' |
|||
@ -0,0 +1,2 @@ |
|||
curl -X POST 'http://example.com' \ |
|||
--data-raw '{}' |
|||
@ -0,0 +1 @@ |
|||
curl 'http://example.com' |
|||
@ -0,0 +1,3 @@ |
|||
curl 'http://example.com' \ |
|||
-H 'x-bb-header-1: 123' \ |
|||
-H 'x-bb-header-2: 456' |
|||
@ -0,0 +1,2 @@ |
|||
curl -X PATCH 'http://example.com/paths/abc' \ |
|||
--data-raw '{ "key" : "val" }' |
|||
@ -0,0 +1 @@ |
|||
curl 'http://example.com/paths/abc' |
|||
@ -0,0 +1,2 @@ |
|||
curl -X POST 'http://example.com' \ |
|||
--data-raw '{ "key" : "val" }' |
|||
@ -0,0 +1,2 @@ |
|||
curl -X PUT 'http://example.com/paths/abc' \ |
|||
--data-raw '{ "key" : "val" }' |
|||
@ -0,0 +1 @@ |
|||
curl 'http://example.com/paths/abc?q1=v1&q1=v2' |
|||
@ -0,0 +1,253 @@ |
|||
{ |
|||
"swagger": "2.0", |
|||
"info": { |
|||
"description": "A basic swagger file", |
|||
"version": "1.0.0", |
|||
"title": "CRUD" |
|||
}, |
|||
"host": "example.com", |
|||
"tags": [ |
|||
{ |
|||
"name": "entity" |
|||
} |
|||
], |
|||
"schemes": [ |
|||
"http" |
|||
], |
|||
"paths": { |
|||
"/entities": { |
|||
"post": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"operationId": "createEntity", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/CreateEntity" |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "successful operation", |
|||
"schema": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"get": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"operationId": "getEntities", |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/Page" |
|||
}, |
|||
{ |
|||
"$ref": "#/parameters/Size" |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "successful operation", |
|||
"schema": { |
|||
"$ref": "#/definitions/Entities" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/entities/{entityId}": { |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/EntityId" |
|||
} |
|||
], |
|||
"get": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"operationId": "getEntity", |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "successful operation", |
|||
"schema": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"put": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"operationId": "updateEntity", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/Entity" |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "successful operation", |
|||
"schema": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"patch": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"operationId": "patchEntity", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/Entity" |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "successful operation", |
|||
"schema": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"delete": { |
|||
"tags": [ |
|||
"entity" |
|||
], |
|||
"parameters": [ |
|||
{ |
|||
"$ref": "#/parameters/APIKey" |
|||
} |
|||
], |
|||
"operationId": "deleteEntity", |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"responses": { |
|||
"204": { |
|||
"description": "successful operation" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"parameters": { |
|||
"EntityId": { |
|||
"type": "integer", |
|||
"format": "int64", |
|||
"name": "entityId", |
|||
"in": "path", |
|||
"required": true |
|||
}, |
|||
"CreateEntity": { |
|||
"name": "entity", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/CreateEntity" |
|||
} |
|||
}, |
|||
"Entity": { |
|||
"name": "entity", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
}, |
|||
"Page": { |
|||
"type": "integer", |
|||
"format": "int32", |
|||
"name": "page", |
|||
"in": "query", |
|||
"required": false |
|||
}, |
|||
"Size": { |
|||
"type": "integer", |
|||
"format": "int32", |
|||
"name": "size", |
|||
"in": "query", |
|||
"required": false |
|||
}, |
|||
"APIKey": { |
|||
"type": "string", |
|||
"name": "x-api-key", |
|||
"in": "header", |
|||
"required": false |
|||
} |
|||
}, |
|||
"definitions": { |
|||
"CreateEntity": { |
|||
"type": "object", |
|||
"properties": { |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"type": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"example": { |
|||
"name": "name", |
|||
"type": "type" |
|||
} |
|||
}, |
|||
"Entity": { |
|||
"allOf": [ |
|||
{ |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "integer", |
|||
"format": "int64" |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"$ref": "#/definitions/CreateEntity" |
|||
} |
|||
], |
|||
"example": { |
|||
"id": 1, |
|||
"name": "name", |
|||
"type": "type" |
|||
} |
|||
}, |
|||
"Entities" : { |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "#/definitions/Entity" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,160 @@ |
|||
--- |
|||
swagger: "2.0" |
|||
info: |
|||
description: A basic swagger file |
|||
version: 1.0.0 |
|||
title: CRUD |
|||
host: example.com |
|||
tags: |
|||
- name: entity |
|||
schemes: |
|||
- http |
|||
paths: |
|||
"/entities": |
|||
post: |
|||
tags: |
|||
- entity |
|||
operationId: createEntity |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
parameters: |
|||
- "$ref": "#/parameters/CreateEntity" |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
"$ref": "#/definitions/Entity" |
|||
get: |
|||
tags: |
|||
- entity |
|||
operationId: getEntities |
|||
produces: |
|||
- application/json |
|||
parameters: |
|||
- "$ref": "#/parameters/Page" |
|||
- "$ref": "#/parameters/Size" |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
"$ref": "#/definitions/Entities" |
|||
"/entities/{entityId}": |
|||
parameters: |
|||
- "$ref": "#/parameters/EntityId" |
|||
get: |
|||
tags: |
|||
- entity |
|||
operationId: getEntity |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
"$ref": "#/definitions/Entity" |
|||
put: |
|||
tags: |
|||
- entity |
|||
operationId: updateEntity |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
parameters: |
|||
- "$ref": "#/parameters/Entity" |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
"$ref": "#/definitions/Entity" |
|||
patch: |
|||
tags: |
|||
- entity |
|||
operationId: patchEntity |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
parameters: |
|||
- "$ref": "#/parameters/Entity" |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
"$ref": "#/definitions/Entity" |
|||
delete: |
|||
tags: |
|||
- entity |
|||
parameters: |
|||
- "$ref": "#/parameters/APIKey" |
|||
operationId: deleteEntity |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"204": |
|||
description: successful operation |
|||
parameters: |
|||
EntityId: |
|||
type: integer |
|||
format: int64 |
|||
name: entityId |
|||
in: path |
|||
required: true |
|||
CreateEntity: |
|||
name: entity |
|||
in: body |
|||
required: true |
|||
schema: |
|||
"$ref": "#/definitions/CreateEntity" |
|||
Entity: |
|||
name: entity |
|||
in: body |
|||
required: true |
|||
schema: |
|||
"$ref": "#/definitions/Entity" |
|||
Page: |
|||
type: integer |
|||
format: int32 |
|||
name: page |
|||
in: query |
|||
required: false |
|||
Size: |
|||
type: integer |
|||
format: int32 |
|||
name: size |
|||
in: query |
|||
required: false |
|||
APIKey: |
|||
type: string |
|||
name: x-api-key |
|||
in: header |
|||
required: false |
|||
definitions: |
|||
CreateEntity: |
|||
type: object |
|||
properties: |
|||
name: |
|||
type: string |
|||
type: |
|||
type: string |
|||
example: |
|||
name: name |
|||
type: type |
|||
Entity: |
|||
allOf: |
|||
- type: object |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
- "$ref": "#/definitions/CreateEntity" |
|||
example: |
|||
id: 1 |
|||
name: name |
|||
type: type |
|||
Entities: |
|||
type: array |
|||
items: |
|||
"$ref": "#/definitions/Entity" |
|||
File diff suppressed because it is too large
@ -0,0 +1,711 @@ |
|||
swagger: "2.0" |
|||
info: |
|||
description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters." |
|||
version: 1.0.5 |
|||
title: Swagger Petstore |
|||
termsOfService: http://swagger.io/terms/ |
|||
contact: |
|||
email: apiteam@swagger.io |
|||
license: |
|||
name: Apache 2.0 |
|||
url: http://www.apache.org/licenses/LICENSE-2.0.html |
|||
host: petstore.swagger.io |
|||
basePath: /v2 |
|||
tags: |
|||
- name: pet |
|||
description: Everything about your Pets |
|||
externalDocs: |
|||
description: Find out more |
|||
url: http://swagger.io |
|||
- name: store |
|||
description: Access to Petstore orders |
|||
- name: user |
|||
description: Operations about user |
|||
externalDocs: |
|||
description: Find out more about our store |
|||
url: http://swagger.io |
|||
schemes: |
|||
- https |
|||
- http |
|||
paths: |
|||
/pet/{petId}/uploadImage: |
|||
post: |
|||
tags: |
|||
- pet |
|||
summary: uploads an image |
|||
description: "" |
|||
operationId: uploadFile |
|||
consumes: |
|||
- multipart/form-data |
|||
produces: |
|||
- application/json |
|||
parameters: |
|||
- name: petId |
|||
in: path |
|||
description: ID of pet to update |
|||
required: true |
|||
type: integer |
|||
format: int64 |
|||
- name: additionalMetadata |
|||
in: formData |
|||
description: Additional data to pass to server |
|||
required: false |
|||
type: string |
|||
- name: file |
|||
in: formData |
|||
description: file to upload |
|||
required: false |
|||
type: file |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
$ref: "#/definitions/ApiResponse" |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
/pet: |
|||
post: |
|||
tags: |
|||
- pet |
|||
summary: Add a new pet to the store |
|||
description: "" |
|||
operationId: addPet |
|||
consumes: |
|||
- application/json |
|||
- application/xml |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: Pet object that needs to be added to the store |
|||
required: true |
|||
schema: |
|||
$ref: "#/definitions/Pet" |
|||
responses: |
|||
"405": |
|||
description: Invalid input |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
put: |
|||
tags: |
|||
- pet |
|||
summary: Update an existing pet |
|||
description: "" |
|||
operationId: updatePet |
|||
consumes: |
|||
- application/json |
|||
- application/xml |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: Pet object that needs to be added to the store |
|||
required: true |
|||
schema: |
|||
$ref: "#/definitions/Pet" |
|||
responses: |
|||
"400": |
|||
description: Invalid ID supplied |
|||
"404": |
|||
description: Pet not found |
|||
"405": |
|||
description: Validation exception |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
/pet/findByStatus: |
|||
get: |
|||
tags: |
|||
- pet |
|||
summary: Finds Pets by status |
|||
description: Multiple status values can be provided with comma separated strings |
|||
operationId: findPetsByStatus |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: status |
|||
in: query |
|||
description: Status values that need to be considered for filter |
|||
required: true |
|||
type: array |
|||
items: |
|||
type: string |
|||
enum: |
|||
- available |
|||
- pending |
|||
- sold |
|||
default: available |
|||
collectionFormat: multi |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
type: array |
|||
items: |
|||
$ref: "#/definitions/Pet" |
|||
"400": |
|||
description: Invalid status value |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
/pet/findByTags: |
|||
get: |
|||
tags: |
|||
- pet |
|||
summary: Finds Pets by tags |
|||
description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. |
|||
operationId: findPetsByTags |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: tags |
|||
in: query |
|||
description: Tags to filter by |
|||
required: true |
|||
type: array |
|||
items: |
|||
type: string |
|||
collectionFormat: multi |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
type: array |
|||
items: |
|||
$ref: "#/definitions/Pet" |
|||
"400": |
|||
description: Invalid tag value |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
deprecated: true |
|||
/pet/{petId}: |
|||
get: |
|||
tags: |
|||
- pet |
|||
summary: Find pet by ID |
|||
description: Returns a single pet |
|||
operationId: getPetById |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: petId |
|||
in: path |
|||
description: ID of pet to return |
|||
required: true |
|||
type: integer |
|||
format: int64 |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
$ref: "#/definitions/Pet" |
|||
"400": |
|||
description: Invalid ID supplied |
|||
"404": |
|||
description: Pet not found |
|||
security: |
|||
- api_key: [] |
|||
post: |
|||
tags: |
|||
- pet |
|||
summary: Updates a pet in the store with form data |
|||
description: "" |
|||
operationId: updatePetWithForm |
|||
consumes: |
|||
- application/x-www-form-urlencoded |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: petId |
|||
in: path |
|||
description: ID of pet that needs to be updated |
|||
required: true |
|||
type: integer |
|||
format: int64 |
|||
- name: name |
|||
in: formData |
|||
description: Updated name of the pet |
|||
required: false |
|||
type: string |
|||
- name: status |
|||
in: formData |
|||
description: Updated status of the pet |
|||
required: false |
|||
type: string |
|||
responses: |
|||
"405": |
|||
description: Invalid input |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
delete: |
|||
tags: |
|||
- pet |
|||
summary: Deletes a pet |
|||
description: "" |
|||
operationId: deletePet |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: api_key |
|||
in: header |
|||
required: false |
|||
type: string |
|||
- name: petId |
|||
in: path |
|||
description: Pet id to delete |
|||
required: true |
|||
type: integer |
|||
format: int64 |
|||
responses: |
|||
"400": |
|||
description: Invalid ID supplied |
|||
"404": |
|||
description: Pet not found |
|||
security: |
|||
- petstore_auth: |
|||
- write:pets |
|||
- read:pets |
|||
/store/inventory: |
|||
get: |
|||
tags: |
|||
- store |
|||
summary: Returns pet inventories by status |
|||
description: Returns a map of status codes to quantities |
|||
operationId: getInventory |
|||
produces: |
|||
- application/json |
|||
parameters: [] |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
type: object |
|||
additionalProperties: |
|||
type: integer |
|||
format: int32 |
|||
security: |
|||
- api_key: [] |
|||
/store/order: |
|||
post: |
|||
tags: |
|||
- store |
|||
summary: Place an order for a pet |
|||
description: "" |
|||
operationId: placeOrder |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: order placed for purchasing the pet |
|||
required: true |
|||
schema: |
|||
$ref: "#/definitions/Order" |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
$ref: "#/definitions/Order" |
|||
"400": |
|||
description: Invalid Order |
|||
/store/order/{orderId}: |
|||
get: |
|||
tags: |
|||
- store |
|||
summary: Find purchase order by ID |
|||
description: For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions |
|||
operationId: getOrderById |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: orderId |
|||
in: path |
|||
description: ID of pet that needs to be fetched |
|||
required: true |
|||
type: integer |
|||
maximum: 10 |
|||
minimum: 1 |
|||
format: int64 |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
$ref: "#/definitions/Order" |
|||
"400": |
|||
description: Invalid ID supplied |
|||
"404": |
|||
description: Order not found |
|||
delete: |
|||
tags: |
|||
- store |
|||
summary: Delete purchase order by ID |
|||
description: For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors |
|||
operationId: deleteOrder |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: orderId |
|||
in: path |
|||
description: ID of the order that needs to be deleted |
|||
required: true |
|||
type: integer |
|||
minimum: 1 |
|||
format: int64 |
|||
responses: |
|||
"400": |
|||
description: Invalid ID supplied |
|||
"404": |
|||
description: Order not found |
|||
/user/createWithList: |
|||
post: |
|||
tags: |
|||
- user |
|||
summary: Creates list of users with given input array |
|||
description: "" |
|||
operationId: createUsersWithListInput |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: List of user object |
|||
required: true |
|||
schema: |
|||
type: array |
|||
items: |
|||
$ref: "#/definitions/User" |
|||
responses: |
|||
default: |
|||
description: successful operation |
|||
/user/{username}: |
|||
get: |
|||
tags: |
|||
- user |
|||
summary: Get user by user name |
|||
description: "" |
|||
operationId: getUserByName |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: username |
|||
in: path |
|||
description: "The name that needs to be fetched. Use user1 for testing. " |
|||
required: true |
|||
type: string |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
schema: |
|||
$ref: "#/definitions/User" |
|||
"400": |
|||
description: Invalid username supplied |
|||
"404": |
|||
description: User not found |
|||
put: |
|||
tags: |
|||
- user |
|||
summary: Updated user |
|||
description: This can only be done by the logged in user. |
|||
operationId: updateUser |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: username |
|||
in: path |
|||
description: name that need to be updated |
|||
required: true |
|||
type: string |
|||
- in: body |
|||
name: body |
|||
description: Updated user object |
|||
required: true |
|||
schema: |
|||
$ref: "#/definitions/User" |
|||
responses: |
|||
"400": |
|||
description: Invalid user supplied |
|||
"404": |
|||
description: User not found |
|||
delete: |
|||
tags: |
|||
- user |
|||
summary: Delete user |
|||
description: This can only be done by the logged in user. |
|||
operationId: deleteUser |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: username |
|||
in: path |
|||
description: The name that needs to be deleted |
|||
required: true |
|||
type: string |
|||
responses: |
|||
"400": |
|||
description: Invalid username supplied |
|||
"404": |
|||
description: User not found |
|||
/user/login: |
|||
get: |
|||
tags: |
|||
- user |
|||
summary: Logs user into the system |
|||
description: "" |
|||
operationId: loginUser |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- name: username |
|||
in: query |
|||
description: The user name for login |
|||
required: true |
|||
type: string |
|||
- name: password |
|||
in: query |
|||
description: The password for login in clear text |
|||
required: true |
|||
type: string |
|||
responses: |
|||
"200": |
|||
description: successful operation |
|||
headers: |
|||
X-Expires-After: |
|||
type: string |
|||
format: date-time |
|||
description: date in UTC when token expires |
|||
X-Rate-Limit: |
|||
type: integer |
|||
format: int32 |
|||
description: calls per hour allowed by the user |
|||
schema: |
|||
type: string |
|||
"400": |
|||
description: Invalid username/password supplied |
|||
/user/logout: |
|||
get: |
|||
tags: |
|||
- user |
|||
summary: Logs out current logged in user session |
|||
description: "" |
|||
operationId: logoutUser |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: [] |
|||
responses: |
|||
default: |
|||
description: successful operation |
|||
/user/createWithArray: |
|||
post: |
|||
tags: |
|||
- user |
|||
summary: Creates list of users with given input array |
|||
description: "" |
|||
operationId: createUsersWithArrayInput |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: List of user object |
|||
required: true |
|||
schema: |
|||
type: array |
|||
items: |
|||
$ref: "#/definitions/User" |
|||
responses: |
|||
default: |
|||
description: successful operation |
|||
/user: |
|||
post: |
|||
tags: |
|||
- user |
|||
summary: Create user |
|||
description: This can only be done by the logged in user. |
|||
operationId: createUser |
|||
consumes: |
|||
- application/json |
|||
produces: |
|||
- application/json |
|||
- application/xml |
|||
parameters: |
|||
- in: body |
|||
name: body |
|||
description: Created user object |
|||
required: true |
|||
schema: |
|||
$ref: "#/definitions/User" |
|||
responses: |
|||
default: |
|||
description: successful operation |
|||
securityDefinitions: |
|||
api_key: |
|||
type: apiKey |
|||
name: api_key |
|||
in: header |
|||
petstore_auth: |
|||
type: oauth2 |
|||
authorizationUrl: https://petstore.swagger.io/oauth/authorize |
|||
flow: implicit |
|||
scopes: |
|||
read:pets: read your pets |
|||
write:pets: modify pets in your account |
|||
definitions: |
|||
ApiResponse: |
|||
type: object |
|||
properties: |
|||
code: |
|||
type: integer |
|||
format: int32 |
|||
type: |
|||
type: string |
|||
message: |
|||
type: string |
|||
Category: |
|||
type: object |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
name: |
|||
type: string |
|||
xml: |
|||
name: Category |
|||
Pet: |
|||
type: object |
|||
required: |
|||
- name |
|||
- photoUrls |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
category: |
|||
$ref: "#/definitions/Category" |
|||
name: |
|||
type: string |
|||
example: doggie |
|||
photoUrls: |
|||
type: array |
|||
xml: |
|||
wrapped: true |
|||
items: |
|||
type: string |
|||
xml: |
|||
name: photoUrl |
|||
tags: |
|||
type: array |
|||
xml: |
|||
wrapped: true |
|||
items: |
|||
xml: |
|||
name: tag |
|||
$ref: "#/definitions/Tag" |
|||
status: |
|||
type: string |
|||
description: pet status in the store |
|||
enum: |
|||
- available |
|||
- pending |
|||
- sold |
|||
xml: |
|||
name: Pet |
|||
Tag: |
|||
type: object |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
name: |
|||
type: string |
|||
xml: |
|||
name: Tag |
|||
Order: |
|||
type: object |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
petId: |
|||
type: integer |
|||
format: int64 |
|||
quantity: |
|||
type: integer |
|||
format: int32 |
|||
shipDate: |
|||
type: string |
|||
format: date-time |
|||
status: |
|||
type: string |
|||
description: Order Status |
|||
enum: |
|||
- placed |
|||
- approved |
|||
- delivered |
|||
complete: |
|||
type: boolean |
|||
xml: |
|||
name: Order |
|||
User: |
|||
type: object |
|||
properties: |
|||
id: |
|||
type: integer |
|||
format: int64 |
|||
username: |
|||
type: string |
|||
firstName: |
|||
type: string |
|||
lastName: |
|||
type: string |
|||
email: |
|||
type: string |
|||
password: |
|||
type: string |
|||
phone: |
|||
type: string |
|||
userStatus: |
|||
type: integer |
|||
format: int32 |
|||
description: User Status |
|||
xml: |
|||
name: User |
|||
externalDocs: |
|||
description: Find out more about Swagger |
|||
url: http://swagger.io |
|||
@ -0,0 +1,239 @@ |
|||
const { OpenAPI2 } = require("../../openapi2") |
|||
const fs = require("fs") |
|||
const path = require('path') |
|||
|
|||
const getData = (file, extension) => { |
|||
return fs.readFileSync(path.join(__dirname, `./data/${file}/${file}.${extension}`), "utf8") |
|||
} |
|||
|
|||
describe("OpenAPI2 Import", () => { |
|||
let openapi2 |
|||
|
|||
beforeEach(() => { |
|||
openapi2 = new OpenAPI2() |
|||
}) |
|||
|
|||
it("validates unsupported data", async () => { |
|||
let data |
|||
let supported |
|||
|
|||
// non json / yaml
|
|||
data = "curl http://example.com" |
|||
supported = await openapi2.isSupported(data) |
|||
expect(supported).toBe(false) |
|||
|
|||
// Empty
|
|||
data = "" |
|||
supported = await openapi2.isSupported(data) |
|||
expect(supported).toBe(false) |
|||
}) |
|||
|
|||
const init = async (file, extension) => { |
|||
await openapi2.isSupported(getData(file, extension)) |
|||
} |
|||
|
|||
const runTests = async (filename, test, assertions) => { |
|||
for (let extension of ["json", "yaml"]) { |
|||
await test(filename, extension, assertions) |
|||
} |
|||
} |
|||
|
|||
const testImportInfo = async (file, extension) => { |
|||
await init(file, extension) |
|||
const info = await openapi2.getInfo() |
|||
expect(info.url).toBe("https://petstore.swagger.io/v2") |
|||
expect(info.name).toBe("Swagger Petstore") |
|||
} |
|||
|
|||
it("returns import info", async () => { |
|||
await runTests("petstore", testImportInfo) |
|||
}) |
|||
|
|||
describe("Returns queries", () => { |
|||
const indexQueries = (queries) => { |
|||
return queries.reduce((acc, query) => { |
|||
acc[query.name] = query |
|||
return acc |
|||
}, {}) |
|||
} |
|||
|
|||
const getQueries = async (file, extension) => { |
|||
await init(file, extension) |
|||
const queries = await openapi2.getQueries() |
|||
expect(queries.length).toBe(6) |
|||
return indexQueries(queries) |
|||
} |
|||
|
|||
const testVerb = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, method] of Object.entries(assertions)) { |
|||
expect(queries[operationId].queryVerb).toBe(method) |
|||
} |
|||
} |
|||
|
|||
it("populates verb", async () => { |
|||
const assertions = { |
|||
"createEntity" : "create", |
|||
"getEntities" : "read", |
|||
"getEntity" : "read", |
|||
"updateEntity" : "update", |
|||
"patchEntity" : "patch", |
|||
"deleteEntity" : "delete" |
|||
} |
|||
await runTests("crud", testVerb, assertions) |
|||
}) |
|||
|
|||
const testPath = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, urlPath] of Object.entries(assertions)) { |
|||
expect(queries[operationId].fields.path).toBe(urlPath) |
|||
} |
|||
} |
|||
|
|||
it("populates path", async () => { |
|||
const assertions = { |
|||
"createEntity" : "entities", |
|||
"getEntities" : "entities", |
|||
"getEntity" : "entities/{{entityId}}", |
|||
"updateEntity" : "entities/{{entityId}}", |
|||
"patchEntity" : "entities/{{entityId}}", |
|||
"deleteEntity" : "entities/{{entityId}}" |
|||
} |
|||
await runTests("crud", testPath, assertions) |
|||
}) |
|||
|
|||
const testHeaders = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, headers] of Object.entries(assertions)) { |
|||
expect(queries[operationId].fields.headers).toStrictEqual(headers) |
|||
} |
|||
} |
|||
|
|||
const contentTypeHeader = { |
|||
"Content-Type" : "application/json", |
|||
} |
|||
|
|||
it("populates headers", async () => { |
|||
const assertions = { |
|||
"createEntity" : { |
|||
...contentTypeHeader |
|||
}, |
|||
"getEntities" : { |
|||
}, |
|||
"getEntity" : { |
|||
}, |
|||
"updateEntity" : { |
|||
...contentTypeHeader |
|||
}, |
|||
"patchEntity" : { |
|||
...contentTypeHeader |
|||
}, |
|||
"deleteEntity" : { |
|||
"x-api-key" : "{{x-api-key}}", |
|||
} |
|||
} |
|||
|
|||
await runTests("crud", testHeaders, assertions) |
|||
}) |
|||
|
|||
const testQuery = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, queryString] of Object.entries(assertions)) { |
|||
expect(queries[operationId].fields.queryString).toStrictEqual(queryString) |
|||
} |
|||
} |
|||
|
|||
it("populates query", async () => { |
|||
const assertions = { |
|||
"createEntity" : "", |
|||
"getEntities" : "page={{page}}&size={{size}}", |
|||
"getEntity" : "", |
|||
"updateEntity" : "", |
|||
"patchEntity" : "", |
|||
"deleteEntity" : "" |
|||
} |
|||
await runTests("crud", testQuery, assertions) |
|||
}) |
|||
|
|||
const testParameters = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, parameters] of Object.entries(assertions)) { |
|||
expect(queries[operationId].parameters).toStrictEqual(parameters) |
|||
} |
|||
} |
|||
|
|||
it("populates parameters", async () => { |
|||
const assertions = { |
|||
"createEntity" : [], |
|||
"getEntities" : [ |
|||
{ |
|||
"name" : "page", |
|||
"default" : "", |
|||
}, |
|||
{ |
|||
"name" : "size", |
|||
"default" : "", |
|||
} |
|||
], |
|||
"getEntity" : [ |
|||
{ |
|||
"name" : "entityId", |
|||
"default" : "", |
|||
} |
|||
], |
|||
"updateEntity" : [ |
|||
{ |
|||
"name" : "entityId", |
|||
"default" : "", |
|||
} |
|||
], |
|||
"patchEntity" : [ |
|||
{ |
|||
"name" : "entityId", |
|||
"default" : "", |
|||
} |
|||
], |
|||
"deleteEntity" : [ |
|||
{ |
|||
"name" : "entityId", |
|||
"default" : "", |
|||
}, |
|||
{ |
|||
"name" : "x-api-key", |
|||
"default" : "", |
|||
} |
|||
] |
|||
} |
|||
await runTests("crud", testParameters, assertions) |
|||
}) |
|||
|
|||
const testBody = async (file, extension, assertions) => { |
|||
const queries = await getQueries(file, extension) |
|||
for (let [operationId, body] of Object.entries(assertions)) { |
|||
expect(queries[operationId].fields.requestBody).toStrictEqual(JSON.stringify(body, null, 2)) |
|||
} |
|||
} |
|||
it("populates body", async () => { |
|||
const assertions = { |
|||
"createEntity" : { |
|||
"name" : "name", |
|||
"type" : "type", |
|||
}, |
|||
"getEntities" : undefined, |
|||
"getEntity" : undefined, |
|||
"updateEntity" : { |
|||
"id": 1, |
|||
"name" : "name", |
|||
"type" : "type", |
|||
}, |
|||
"patchEntity" : { |
|||
"id": 1, |
|||
"name" : "name", |
|||
"type" : "type", |
|||
}, |
|||
"deleteEntity" : undefined |
|||
} |
|||
await runTests("crud", testBody, assertions) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,115 @@ |
|||
|
|||
const bulkDocs = jest.fn() |
|||
const db = jest.fn(() => { |
|||
return { |
|||
bulkDocs |
|||
} |
|||
}) |
|||
jest.mock("../../../../../db", () => db) |
|||
|
|||
const { RestImporter } = require("../index") |
|||
|
|||
const fs = require("fs") |
|||
const path = require('path') |
|||
|
|||
const getData = (file) => { |
|||
return fs.readFileSync(path.join(__dirname, `../sources/tests/${file}`), "utf8") |
|||
} |
|||
|
|||
// openapi2 (swagger)
|
|||
const oapi2CrudJson = getData("openapi2/data/crud/crud.json") |
|||
const oapi2CrudYaml = getData("openapi2/data/crud/crud.json") |
|||
const oapi2PetstoreJson = getData("openapi2/data/petstore/petstore.json") |
|||
const oapi2PetstoreYaml = getData("openapi2/data/petstore/petstore.json") |
|||
|
|||
// curl
|
|||
const curl = getData("curl/data/post.txt") |
|||
|
|||
const datasets = { |
|||
oapi2CrudJson, |
|||
oapi2CrudYaml, |
|||
oapi2PetstoreJson, |
|||
oapi2PetstoreYaml, |
|||
curl |
|||
} |
|||
|
|||
describe("Rest Importer", () => { |
|||
let restImporter |
|||
|
|||
const init = async (data) => { |
|||
restImporter = new RestImporter(data) |
|||
await restImporter.init() |
|||
} |
|||
|
|||
const runTest = async (test, assertions) => { |
|||
for (let [key, data] of Object.entries(datasets)) { |
|||
await test(key, data, assertions) |
|||
} |
|||
} |
|||
|
|||
const testGetInfo = async (key, data, assertions) => { |
|||
await init(data) |
|||
const info = await restImporter.getInfo() |
|||
expect(info.name).toBe(assertions[key].name) |
|||
expect(info.url).toBe(assertions[key].url) |
|||
} |
|||
|
|||
it("gets info", async () => { |
|||
const assertions = { |
|||
"oapi2CrudJson" : { |
|||
name: "CRUD", |
|||
url: "http://example.com" |
|||
}, |
|||
"oapi2CrudYaml" : { |
|||
name: "CRUD", |
|||
url: "http://example.com" |
|||
}, |
|||
"oapi2PetstoreJson" : { |
|||
name: "Swagger Petstore", |
|||
url: "https://petstore.swagger.io/v2" |
|||
}, |
|||
"oapi2PetstoreYaml" :{ |
|||
name: "Swagger Petstore", |
|||
url: "https://petstore.swagger.io/v2" |
|||
}, |
|||
"curl": { |
|||
name: "example.com", |
|||
url: "http://example.com" |
|||
} |
|||
} |
|||
await runTest(testGetInfo, assertions) |
|||
}) |
|||
|
|||
const testImportQueries = async (key, data, assertions) => { |
|||
await init(data) |
|||
bulkDocs.mockReturnValue([]) |
|||
const importResult = await restImporter.importQueries("appId", "datasourceId") |
|||
expect(importResult.errorQueries.length).toBe(0) |
|||
expect(importResult.queries.length).toBe(assertions[key].count) |
|||
expect(bulkDocs).toHaveBeenCalledTimes(1) |
|||
jest.clearAllMocks() |
|||
} |
|||
|
|||
it("imports queries", async () => { |
|||
// simple sanity assertions that the whole dataset
|
|||
// makes it through the importer
|
|||
const assertions = { |
|||
"oapi2CrudJson" : { |
|||
count: 6, |
|||
}, |
|||
"oapi2CrudYaml" :{ |
|||
count: 6, |
|||
}, |
|||
"oapi2PetstoreJson" : { |
|||
count: 20, |
|||
}, |
|||
"oapi2PetstoreYaml" :{ |
|||
count: 20, |
|||
}, |
|||
"curl": { |
|||
count: 1 |
|||
} |
|||
} |
|||
await runTest(testImportQueries, assertions) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,40 @@ |
|||
const joiValidator = require("../../../middleware/joi-validator") |
|||
const Joi = require("joi") |
|||
|
|||
exports.queryValidation = () => { |
|||
return Joi.object({ |
|||
_id: Joi.string(), |
|||
_rev: Joi.string(), |
|||
name: Joi.string().required(), |
|||
fields: Joi.object().required(), |
|||
datasourceId: Joi.string().required(), |
|||
readable: Joi.boolean(), |
|||
parameters: Joi.array().items( |
|||
Joi.object({ |
|||
name: Joi.string(), |
|||
default: Joi.string().allow(""), |
|||
}) |
|||
), |
|||
queryVerb: Joi.string().allow().required(), |
|||
extra: Joi.object().optional(), |
|||
schema: Joi.object({}).required().unknown(true), |
|||
transformer: Joi.string().optional(), |
|||
}) |
|||
} |
|||
|
|||
exports.generateQueryValidation = () => { |
|||
// prettier-ignore
|
|||
return joiValidator.body(exports.queryValidation()) |
|||
} |
|||
|
|||
exports.generateQueryPreviewValidation = () => { |
|||
// prettier-ignore
|
|||
return joiValidator.body(Joi.object({ |
|||
fields: Joi.object().required(), |
|||
queryVerb: Joi.string().allow().required(), |
|||
extra: Joi.object().optional(), |
|||
datasourceId: Joi.string().required(), |
|||
transformer: Joi.string().optional(), |
|||
parameters: Joi.object({}).required().unknown(true) |
|||
})) |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue