Adding a testing system which generates the schema and compares against responses.

This commit is contained in:
Michael Drury 2022-02-24 23:21:10 +00:00
parent f2c2c903e5
commit 46d23cfb25
17 changed files with 2698 additions and 2630 deletions

View File

@ -53,10 +53,10 @@
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@budibase/string-templates@^1.0.66-alpha.0":
version "1.0.72"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.72.tgz#acc154e402cce98ea30eedde9c6124183ee9b37c"
integrity sha512-w715TjgO6NUHkZNqoOEo8lAKJ/PQ4b00ATWSX5VB523SAu7y/uOiqKqV1E3fgwxq1o8L+Ff7rn9FTkiYtjkV/g==
"@budibase/string-templates@^1.0.72-alpha.0":
version "1.0.75"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.75.tgz#5b4061f1a626160ec092f32f036541376298100c"
integrity sha512-hPgr6n5cpSCGFEha5DS/P+rtRXOLc72M6y4J/scl59JvUi/ZUJkjRgJdpQPdBLu04CNKp89V59+rAqAuDjOC0g==
dependencies:
"@budibase/handlebars-helpers" "^0.11.7"
dayjs "^1.10.4"

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,7 @@ module FetchMock {
{
doc: {
_id: "test",
tableId: opts.body.split("tableId:")[1].split('"')[0],
},
},
],

View File

@ -158,6 +158,7 @@
"docker-compose": "^0.23.6",
"eslint": "^6.8.0",
"jest": "^27.0.5",
"jest-openapi": "^0.14.2",
"nodemon": "^2.0.4",
"openapi-types": "^9.3.1",
"path-to-regexp": "^6.2.0",

View File

@ -49,12 +49,11 @@ const options = {
apis: [join(__dirname, "..", "src", "api", "routes", "public", "*.ts")],
}
function writeFile(output, { isJson } = {}) {
function writeFile(output, filename) {
try {
const filename = isJson ? "openapi.json" : "openapi.yaml"
const path = join(__dirname, filename)
let spec = output
if (isJson) {
if (filename.endsWith("json")) {
spec = JSON.stringify(output, null, 2)
}
// input the static variables
@ -63,13 +62,22 @@ function writeFile(output, { isJson } = {}) {
}
writeFileSync(path, spec)
console.log(`Wrote spec to ${path}`)
return path
} catch (err) {
console.error(err)
}
}
function run() {
const outputJSON = swaggerJsdoc(options)
options.format = ".yaml"
const outputYAML = swaggerJsdoc(options)
writeFile(outputJSON, { isJson: true })
writeFile(outputYAML)
writeFile(outputJSON, "openapi.json")
return writeFile(outputYAML, "openapi.yaml")
}
if (require.main === module) {
run()
}
module.exports = run

View File

@ -474,7 +474,10 @@
"url"
]
}
}
},
"required": [
"application"
]
},
"row": {
"description": "The row to be created/updated, based on the table schema.",
@ -525,11 +528,18 @@
]
}
}
}
},
"required": [
"row"
]
},
"table": {
"description": "The table to be created/updated.",
"type": "object",
"required": [
"name",
"schema"
],
"properties": {
"name": {
"description": "The name of the table",
@ -540,6 +550,8 @@
"description": "The name of the column which should be used in relationship tags when relating to this table."
},
"schema": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
@ -718,6 +730,7 @@
]
}
}
}
},
"tableOutput": {
"type": "object",
@ -725,6 +738,10 @@
"table": {
"description": "The table to be created/updated.",
"type": "object",
"required": [
"name",
"schema"
],
"properties": {
"name": {
"description": "The name of the table",
@ -735,6 +752,8 @@
"description": "The name of the column which should be used in relationship tags when relating to this table."
},
"schema": {
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
@ -916,23 +935,33 @@
}
}
},
"required": [
"table"
]
},
"query": {
"type": "object",
"properties": {}
"properties": {},
"required": []
},
"user": {
"type": "object",
"properties": {}
"properties": {},
"required": []
},
"userOutput": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {}
}
"properties": {},
"required": []
}
},
"required": [
"user"
]
},
"nameSearch": {
"type": "object",
"properties": {
@ -940,7 +969,10 @@
"type": "string",
"description": "The name to be used when searching - this will be used in a case insensitive starts with match."
}
}
},
"required": [
"name"
]
}
}
},
@ -950,54 +982,6 @@
}
],
"paths": {
"/applications/search": {
"post": {
"summary": "Search for an application based on its app name.",
"tags": [
"applications"
],
"parameters": [
{
"$ref": "#/components/parameters/appId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/nameSearch"
}
}
}
},
"responses": {
"200": {
"description": "Returns the applications that were found based on the search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"applications": {
"type": "array",
"items": {
"$ref": "#/components/schemas/application"
}
}
}
},
"examples": {
"applications": {
"$ref": "#/components/examples/applications"
}
}
}
}
}
}
}
},
"/applications": {
"post": {
"summary": "Create a new application.",
@ -1134,11 +1118,11 @@
}
}
},
"/queries/search": {
"/applications/search": {
"post": {
"summary": "Search for a query based on its name.",
"summary": "Search for an application based on its app name.",
"tags": [
"queries"
"applications"
],
"parameters": [
{
@ -1157,23 +1141,26 @@
},
"responses": {
"200": {
"description": "Returns the queries found based on the search parameters.",
"description": "Returns the applications that were found based on the search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"applications"
],
"properties": {
"queries": {
"applications": {
"type": "array",
"items": {
"$ref": "#/components/schemas/query"
"$ref": "#/components/schemas/application"
}
}
}
},
"examples": {
"queries": {
"$ref": "#/components/examples/queries"
"applications": {
"$ref": "#/components/examples/applications"
}
}
}
@ -1246,16 +1233,13 @@
}
}
},
"/tables/{tableId}/rows/search": {
"/queries/search": {
"post": {
"summary": "Used to search for rows within a table.",
"summary": "Search for a query based on its name.",
"tags": [
"rows"
"queries"
],
"parameters": [
{
"$ref": "#/components/parameters/tableId"
},
{
"$ref": "#/components/parameters/appId"
}
@ -1265,147 +1249,33 @@
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"query": {
"type": "object",
"properties": {
"string": {
"type": "object",
"example": {
"columnName1": "value",
"columnName2": "value"
},
"description": "A map of field name to the string to search for, this will look for rows that have a value starting with the string value.",
"additionalProperties": {
"type": "string",
"description": "The value to search for in the column."
}
},
"fuzzy": {
"type": "object",
"description": "A fuzzy search, only supported by internal tables."
},
"range": {
"type": "object",
"description": "Searches within a range, the format of this must be columnName -> [low, high].",
"example": {
"columnName1": [
10,
20
]
}
},
"equal": {
"type": "object",
"description": "Searches for rows that have a column value that is exactly the value set."
},
"notEqual": {
"type": "object",
"description": "Searches for any row which does not contain the specified column value."
},
"empty": {
"type": "object",
"description": "Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.",
"example": {
"columnName1": ""
}
},
"notEmpty": {
"type": "object",
"description": "Searches for rows which have the specified column."
},
"oneOf": {
"type": "object",
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
}
}
},
"paginate": {
"type": "boolean",
"description": "Enables pagination, by default this is disabled."
},
"bookmark": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "If retrieving another page, the bookmark from the previous request must be supplied."
},
"limit": {
"type": "integer",
"description": "The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000."
},
"sort": {
"type": "object",
"description": "A set of parameters describing the sort behaviour of the search.",
"properties": {
"order": {
"type": "string",
"enum": [
"ascending",
"descending"
],
"description": "The order of the sort, by default this is ascending."
},
"column": {
"type": "string",
"description": "The name of the column by which the rows will be sorted."
},
"type": {
"type": "string",
"enum": [
"string",
"number"
],
"description": "Defines whether the column should be treated as a string or as numbers when sorting."
}
}
}
}
"$ref": "#/components/schemas/nameSearch"
}
}
}
},
"responses": {
"200": {
"description": "The response will contain an array of rows that match the search parameters.",
"description": "Returns the queries found based on the search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"queries"
],
"properties": {
"rows": {
"description": "An array of rows, these will each contain an _id field which can be used to update or delete them.",
"queries": {
"type": "array",
"items": {
"type": "object"
"$ref": "#/components/schemas/query"
}
},
"bookmark": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "If pagination in use, this should be provided."
},
"hasNextPage": {
"description": "If pagination in use, this will determine if there is another page to fetch.",
"type": "boolean"
}
}
},
"examples": {
"search": {
"$ref": "#/components/examples/rows"
"queries": {
"$ref": "#/components/examples/queries"
}
}
}
@ -1581,13 +1451,16 @@
}
}
},
"/tables/search": {
"/tables/{tableId}/rows/search": {
"post": {
"summary": "Search internal and external tables based on their name.",
"summary": "Used to search for rows within a table.",
"tags": [
"tables"
"rows"
],
"parameters": [
{
"$ref": "#/components/parameters/tableId"
},
{
"$ref": "#/components/parameters/appId"
}
@ -1597,30 +1470,153 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/nameSearch"
"type": "object",
"required": [
"query"
],
"properties": {
"query": {
"type": "object",
"properties": {
"string": {
"type": "object",
"example": {
"columnName1": "value",
"columnName2": "value"
},
"description": "A map of field name to the string to search for, this will look for rows that have a value starting with the string value.",
"additionalProperties": {
"type": "string",
"description": "The value to search for in the column."
}
},
"fuzzy": {
"type": "object",
"description": "A fuzzy search, only supported by internal tables."
},
"range": {
"type": "object",
"description": "Searches within a range, the format of this must be columnName -> [low, high].",
"example": {
"columnName1": [
10,
20
]
}
},
"equal": {
"type": "object",
"description": "Searches for rows that have a column value that is exactly the value set."
},
"notEqual": {
"type": "object",
"description": "Searches for any row which does not contain the specified column value."
},
"empty": {
"type": "object",
"description": "Searches for rows which do not contain the specified column. The object should simply contain keys of the column names, these can map to any value.",
"example": {
"columnName1": ""
}
},
"notEmpty": {
"type": "object",
"description": "Searches for rows which have the specified column."
},
"oneOf": {
"type": "object",
"description": "Searches for rows which have a column value that is any of the specified values. The format of this must be columnName -> [value1, value2]."
}
}
},
"paginate": {
"type": "boolean",
"description": "Enables pagination, by default this is disabled."
},
"bookmark": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "If retrieving another page, the bookmark from the previous request must be supplied."
},
"limit": {
"type": "integer",
"description": "The maximum number of rows to return, useful when paginating, for internal tables this will be limited to 1000, for SQL tables it will be 5000."
},
"sort": {
"type": "object",
"description": "A set of parameters describing the sort behaviour of the search.",
"properties": {
"order": {
"type": "string",
"enum": [
"ascending",
"descending"
],
"description": "The order of the sort, by default this is ascending."
},
"column": {
"type": "string",
"description": "The name of the column by which the rows will be sorted."
},
"type": {
"type": "string",
"enum": [
"string",
"number"
],
"description": "Defines whether the column should be treated as a string or as numbers when sorting."
}
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Returns the found tables, based on the search parameters.",
"description": "The response will contain an array of rows that match the search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"rows"
],
"properties": {
"applications": {
"rows": {
"description": "An array of rows, these will each contain an _id field which can be used to update or delete them.",
"type": "array",
"items": {
"$ref": "#/components/schemas/table"
"type": "object"
}
},
"bookmark": {
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "If pagination in use, this should be provided."
},
"hasNextPage": {
"description": "If pagination in use, this will determine if there is another page to fetch.",
"type": "boolean"
}
}
},
"examples": {
"tables": {
"$ref": "#/components/examples/tables"
"search": {
"$ref": "#/components/examples/rows"
}
}
}
@ -1782,11 +1778,11 @@
}
}
},
"/users/search": {
"/tables/search": {
"post": {
"summary": "Search for a user based on their email/username.",
"summary": "Search internal and external tables based on their name.",
"tags": [
"users"
"tables"
],
"parameters": [
{
@ -1805,18 +1801,26 @@
},
"responses": {
"200": {
"description": "Returns the found users based on search parameters.",
"description": "Returns the found tables, based on the search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"tables"
],
"properties": {
"tables": {
"type": "array",
"items": {
"$ref": "#/components/schemas/user"
"$ref": "#/components/schemas/table"
}
}
}
},
"examples": {
"users": {
"$ref": "#/components/examples/users"
"tables": {
"$ref": "#/components/examples/tables"
}
}
}
@ -1969,6 +1973,57 @@
}
}
}
},
"/users/search": {
"post": {
"summary": "Search for a user based on their email/username.",
"tags": [
"users"
],
"parameters": [
{
"$ref": "#/components/parameters/appId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/nameSearch"
}
}
}
},
"responses": {
"200": {
"description": "Returns the found users based on search parameters.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"users"
],
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "#/components/schemas/user"
}
}
}
},
"examples": {
"users": {
"$ref": "#/components/examples/users"
}
}
}
}
}
}
}
}
},
"tags": []

View File

@ -339,6 +339,8 @@ components:
required:
- name
- url
required:
- application
row:
description: The row to be created/updated, based on the table schema.
type: object
@ -362,9 +364,14 @@ components:
- type: integer
- type: array
- type: boolean
required:
- row
table:
description: The table to be created/updated.
type: object
required:
- name
- schema
properties:
name:
description: The name of the table
@ -374,160 +381,8 @@ components:
description: The name of the column which should be used in relationship tags
when relating to this table.
schema:
oneOf:
- type: object
properties:
type:
type: string
enum:
- link
description: A relationship column.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
fieldName:
type: string
description: The name of the column which a relationship column is related to in
another table.
tableId:
type: string
description: The ID of the table which a relationship column is related to.
relationshipType:
type: string
enum:
- one-to-many
- many-to-one
- many-to-many
description: Defines the type of relationship that this column will be used for.
through:
type: string
description: When using a SQL table that contains many to many relationships
this defines the table the relationships are linked through.
foreignKey:
type: string
description: When using a SQL table that contains a one to many relationship
this defines the foreign key.
throughFrom:
type: string
description: When using a SQL table that utilises a through table, this defines
the primary key in the through table for this table.
throughTo:
type: string
description: When using a SQL table that utilises a through table, this defines
the primary key in the through table for the related table.
- type: object
properties:
type:
type: string
enum:
- formula
description: A formula column.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
formula:
type: string
description: Defines a Handlebars or JavaScript formula to use, note that
Javascript formulas are expected to be provided in the
base64 format.
formulaType:
type: string
enum:
- static
- dynamic
description: Defines whether this is a static or dynamic formula.
- type: object
properties:
type:
type: string
enum:
- string
- longform
- options
- number
- boolean
- array
- datetime
- attachment
- link
- formula
- auto
- json
- internal
description: Defines the type of the column, most explain themselves, a link
column is a relationship.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
tableOutput:
type: object
properties:
table:
description: The table to be created/updated.
type: object
properties:
name:
description: The name of the table
type: string
primaryDisplay:
type: string
description: The name of the column which should be used in relationship tags
when relating to this table.
schema:
additionalProperties:
oneOf:
- type: object
properties:
@ -669,18 +524,186 @@ components:
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
tableOutput:
type: object
properties:
table:
description: The table to be created/updated.
type: object
required:
- name
- schema
properties:
name:
description: The name of the table
type: string
primaryDisplay:
type: string
description: The name of the column which should be used in relationship tags
when relating to this table.
schema:
type: object
additionalProperties:
oneOf:
- type: object
properties:
type:
type: string
enum:
- link
description: A relationship column.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
fieldName:
type: string
description: The name of the column which a relationship column is related to in
another table.
tableId:
type: string
description: The ID of the table which a relationship column is related to.
relationshipType:
type: string
enum:
- one-to-many
- many-to-one
- many-to-many
description: Defines the type of relationship that this column will be used for.
through:
type: string
description: When using a SQL table that contains many to many relationships
this defines the table the relationships are linked
through.
foreignKey:
type: string
description: When using a SQL table that contains a one to many relationship
this defines the foreign key.
throughFrom:
type: string
description: When using a SQL table that utilises a through table, this defines
the primary key in the through table for this table.
throughTo:
type: string
description: When using a SQL table that utilises a through table, this defines
the primary key in the through table for the related
table.
- type: object
properties:
type:
type: string
enum:
- formula
description: A formula column.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
formula:
type: string
description: Defines a Handlebars or JavaScript formula to use, note that
Javascript formulas are expected to be provided in the
base64 format.
formulaType:
type: string
enum:
- static
- dynamic
description: Defines whether this is a static or dynamic formula.
- type: object
properties:
type:
type: string
enum:
- string
- longform
- options
- number
- boolean
- array
- datetime
- attachment
- link
- formula
- auto
- json
- internal
description: Defines the type of the column, most explain themselves, a link
column is a relationship.
constraints:
type: object
description: A constraint can be applied to the column which will be validated
against when a row is saved.
properties:
type:
type: string
enum:
- string
- number
- object
- boolean
presence:
type: boolean
description: Defines whether the column is required or not.
name:
type: string
description: The name of the column.
autocolumn:
type: boolean
description: Defines whether the column is automatically generated.
required:
- table
query:
type: object
properties: {}
required: []
user:
type: object
properties: {}
required: []
userOutput:
type: object
properties:
user:
type: object
properties: {}
required: []
required:
- user
nameSearch:
type: object
properties:
@ -688,38 +711,11 @@ components:
type: string
description: The name to be used when searching - this will be used in a case
insensitive starts with match.
required:
- name
security:
- ApiKeyAuth: []
paths:
/applications/search:
post:
summary: Search for an application based on its app name.
tags:
- applications
parameters:
- $ref: "#/components/parameters/appId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/nameSearch"
responses:
"200":
description: Returns the applications that were found based on the search
parameters.
content:
application/json:
schema:
type: object
properties:
applications:
type: array
items:
$ref: "#/components/schemas/application"
examples:
applications:
$ref: "#/components/examples/applications"
/applications:
post:
summary: Create a new application.
@ -798,11 +794,11 @@ paths:
examples:
application:
$ref: "#/components/examples/application"
/queries/search:
/applications/search:
post:
summary: Search for a query based on its name.
summary: Search for an application based on its app name.
tags:
- queries
- applications
parameters:
- $ref: "#/components/parameters/appId"
requestBody:
@ -813,19 +809,22 @@ paths:
$ref: "#/components/schemas/nameSearch"
responses:
"200":
description: Returns the queries found based on the search parameters.
description: Returns the applications that were found based on the search
parameters.
content:
application/json:
schema:
type: object
required:
- applications
properties:
queries:
applications:
type: array
items:
$ref: "#/components/schemas/query"
$ref: "#/components/schemas/application"
examples:
queries:
$ref: "#/components/examples/queries"
applications:
$ref: "#/components/examples/applications"
"/queries/{queryId}":
post:
summary: Execute a query and retrieve its response.
@ -867,129 +866,36 @@ paths:
$ref: "#/components/examples/restResponse"
SQL:
$ref: "#/components/examples/sqlResponse"
"/tables/{tableId}/rows/search":
/queries/search:
post:
summary: Used to search for rows within a table.
summary: Search for a query based on its name.
tags:
- rows
- queries
parameters:
- $ref: "#/components/parameters/tableId"
- $ref: "#/components/parameters/appId"
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
query:
type: object
properties:
string:
type: object
example:
columnName1: value
columnName2: value
description: A map of field name to the string to search for, this will look for
rows that have a value starting with the string value.
additionalProperties:
type: string
description: The value to search for in the column.
fuzzy:
type: object
description: A fuzzy search, only supported by internal tables.
range:
type: object
description: Searches within a range, the format of this must be columnName ->
[low, high].
example:
columnName1:
- 10
- 20
equal:
type: object
description: Searches for rows that have a column value that is exactly the
value set.
notEqual:
type: object
description: Searches for any row which does not contain the specified column
value.
empty:
type: object
description: Searches for rows which do not contain the specified column. The
object should simply contain keys of the column names,
these can map to any value.
example:
columnName1: ""
notEmpty:
type: object
description: Searches for rows which have the specified column.
oneOf:
type: object
description: Searches for rows which have a column value that is any of the
specified values. The format of this must be columnName
-> [value1, value2].
paginate:
type: boolean
description: Enables pagination, by default this is disabled.
bookmark:
oneOf:
- type: string
- type: integer
description: If retrieving another page, the bookmark from the previous request
must be supplied.
limit:
type: integer
description: The maximum number of rows to return, useful when paginating, for
internal tables this will be limited to 1000, for SQL tables
it will be 5000.
sort:
type: object
description: A set of parameters describing the sort behaviour of the search.
properties:
order:
type: string
enum:
- ascending
- descending
description: The order of the sort, by default this is ascending.
column:
type: string
description: The name of the column by which the rows will be sorted.
type:
type: string
enum:
- string
- number
description: Defines whether the column should be treated as a string or as
numbers when sorting.
$ref: "#/components/schemas/nameSearch"
responses:
"200":
description: The response will contain an array of rows that match the search
parameters.
description: Returns the queries found based on the search parameters.
content:
application/json:
schema:
type: object
required:
- queries
properties:
rows:
description: An array of rows, these will each contain an _id field which can be
used to update or delete them.
queries:
type: array
items:
type: object
bookmark:
oneOf:
- type: string
- type: integer
description: If pagination in use, this should be provided.
hasNextPage:
description: If pagination in use, this will determine if there is another page
to fetch.
type: boolean
$ref: "#/components/schemas/query"
examples:
search:
$ref: "#/components/examples/rows"
queries:
$ref: "#/components/examples/queries"
"/tables/{tableId}/rows":
post:
summary: Creates a new row within a specified table.
@ -1085,34 +991,133 @@ paths:
examples:
row:
$ref: "#/components/examples/row"
/tables/search:
"/tables/{tableId}/rows/search":
post:
summary: Search internal and external tables based on their name.
summary: Used to search for rows within a table.
tags:
- tables
- rows
parameters:
- $ref: "#/components/parameters/tableId"
- $ref: "#/components/parameters/appId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/nameSearch"
type: object
required:
- query
properties:
query:
type: object
properties:
string:
type: object
example:
columnName1: value
columnName2: value
description: A map of field name to the string to search for, this will look for
rows that have a value starting with the string value.
additionalProperties:
type: string
description: The value to search for in the column.
fuzzy:
type: object
description: A fuzzy search, only supported by internal tables.
range:
type: object
description: Searches within a range, the format of this must be columnName ->
[low, high].
example:
columnName1:
- 10
- 20
equal:
type: object
description: Searches for rows that have a column value that is exactly the
value set.
notEqual:
type: object
description: Searches for any row which does not contain the specified column
value.
empty:
type: object
description: Searches for rows which do not contain the specified column. The
object should simply contain keys of the column names,
these can map to any value.
example:
columnName1: ""
notEmpty:
type: object
description: Searches for rows which have the specified column.
oneOf:
type: object
description: Searches for rows which have a column value that is any of the
specified values. The format of this must be columnName
-> [value1, value2].
paginate:
type: boolean
description: Enables pagination, by default this is disabled.
bookmark:
oneOf:
- type: string
- type: integer
description: If retrieving another page, the bookmark from the previous request
must be supplied.
limit:
type: integer
description: The maximum number of rows to return, useful when paginating, for
internal tables this will be limited to 1000, for SQL tables
it will be 5000.
sort:
type: object
description: A set of parameters describing the sort behaviour of the search.
properties:
order:
type: string
enum:
- ascending
- descending
description: The order of the sort, by default this is ascending.
column:
type: string
description: The name of the column by which the rows will be sorted.
type:
type: string
enum:
- string
- number
description: Defines whether the column should be treated as a string or as
numbers when sorting.
responses:
"200":
description: Returns the found tables, based on the search parameters.
description: The response will contain an array of rows that match the search
parameters.
content:
application/json:
schema:
type: object
required:
- rows
properties:
applications:
rows:
description: An array of rows, these will each contain an _id field which can be
used to update or delete them.
type: array
items:
$ref: "#/components/schemas/table"
type: object
bookmark:
oneOf:
- type: string
- type: integer
description: If pagination in use, this should be provided.
hasNextPage:
description: If pagination in use, this will determine if there is another page
to fetch.
type: boolean
examples:
tables:
$ref: "#/components/examples/tables"
search:
$ref: "#/components/examples/rows"
/tables:
post:
summary: Create a new table.
@ -1199,11 +1204,11 @@ paths:
examples:
table:
$ref: "#/components/examples/table"
/users/search:
/tables/search:
post:
summary: Search for a user based on their email/username.
summary: Search internal and external tables based on their name.
tags:
- users
- tables
parameters:
- $ref: "#/components/parameters/appId"
requestBody:
@ -1214,16 +1219,21 @@ paths:
$ref: "#/components/schemas/nameSearch"
responses:
"200":
description: Returns the found users based on search parameters.
description: Returns the found tables, based on the search parameters.
content:
application/json:
schema:
type: object
required:
- tables
properties:
tables:
type: array
items:
$ref: "#/components/schemas/user"
$ref: "#/components/schemas/table"
examples:
users:
$ref: "#/components/examples/users"
tables:
$ref: "#/components/examples/tables"
/users:
post:
summary: Create a new user in the Budibase portal.
@ -1305,4 +1315,34 @@ paths:
examples:
user:
$ref: "#/components/examples/user"
/users/search:
post:
summary: Search for a user based on their email/username.
tags:
- users
parameters:
- $ref: "#/components/parameters/appId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/nameSearch"
responses:
"200":
description: Returns the found users based on search parameters.
content:
application/json:
schema:
type: object
required:
- users
properties:
users:
type: array
items:
$ref: "#/components/schemas/user"
examples:
users:
$ref: "#/components/examples/users"
tags: []

View File

@ -63,6 +63,7 @@ const baseColumnDef = {
const tableSchema = {
description: "The table to be created/updated.",
type: "object",
required: ["name", "schema"],
properties: {
name: {
description: "The name of the table",
@ -74,6 +75,8 @@ const tableSchema = {
"The name of the column which should be used in relationship tags when relating to this table.",
},
schema: {
type: "object",
additionalProperties: {
oneOf: [
// relationship
{
@ -152,6 +155,7 @@ const tableSchema = {
],
},
},
},
}
module.exports = new Resource()

View File

@ -2,6 +2,7 @@ exports.object = (props, opts) => {
return {
type: "object",
properties: props,
required: Object.keys(props),
...opts,
}
}

View File

@ -5,43 +5,6 @@ const { nameValidator, applicationValidator } = require("../utils/validators")
const read = [],
write = []
/**
* @openapi
* /applications/search:
* post:
* summary: Search for an application based on its app name.
* tags:
* - applications
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the applications that were found based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* properties:
* applications:
* type: array
* items:
* $ref: '#/components/schemas/application'
* examples:
* applications:
* $ref: '#/components/examples/applications'
*/
read.push(
new Endpoint("post", "/applications/search", controller.search).addMiddleware(
nameValidator()
)
)
/**
* @openapi
* /applications:
@ -68,7 +31,11 @@ read.push(
* application:
* $ref: '#/components/examples/application'
*/
write.push(new Endpoint("post", "/applications", controller.create))
write.push(
new Endpoint("post", "/applications", controller.create).addMiddleware(
applicationValidator
)
)
/**
* @openapi
@ -96,7 +63,11 @@ write.push(new Endpoint("post", "/applications", controller.create))
* application:
* $ref: '#/components/examples/application'
*/
write.push(new Endpoint("put", "/applications/:appId", controller.update))
write.push(
new Endpoint("put", "/applications/:appId", controller.update).addMiddleware(
applicationValidator
)
)
/**
* @openapi
@ -142,4 +113,43 @@ write.push(new Endpoint("delete", "/applications/:appId", controller.destroy))
*/
read.push(new Endpoint("get", "/applications/:appId", controller.read))
/**
* @openapi
* /applications/search:
* post:
* summary: Search for an application based on its app name.
* tags:
* - applications
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the applications that were found based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* required:
* - applications
* properties:
* applications:
* type: array
* items:
* $ref: '#/components/schemas/application'
* examples:
* applications:
* $ref: '#/components/examples/applications'
*/
read.push(
new Endpoint("post", "/applications/search", controller.search).addMiddleware(
nameValidator()
)
)
export default { read, write }

View File

@ -5,43 +5,6 @@ import { nameValidator } from "../utils/validators"
const read = [],
write = []
/**
* @openapi
* /queries/search:
* post:
* summary: Search for a query based on its name.
* tags:
* - queries
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the queries found based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* properties:
* queries:
* type: array
* items:
* $ref: '#/components/schemas/query'
* examples:
* queries:
* $ref: '#/components/examples/queries'
*/
read.push(
new Endpoint("post", "/queries/search", controller.search).addMiddleware(
nameValidator()
)
)
/**
* @openapi
* /queries/{queryId}:
@ -89,4 +52,43 @@ read.push(
*/
write.push(new Endpoint("post", "/queries/:queryId", controller.execute))
/**
* @openapi
* /queries/search:
* post:
* summary: Search for a query based on its name.
* tags:
* - queries
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the queries found based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* required:
* - queries
* properties:
* queries:
* type: array
* items:
* $ref: '#/components/schemas/query'
* examples:
* queries:
* $ref: '#/components/examples/queries'
*/
read.push(
new Endpoint("post", "/queries/search", controller.search).addMiddleware(
nameValidator()
)
)
export default { read, write }

View File

@ -5,130 +5,6 @@ import { externalSearchValidator } from "../utils/validators"
const read = [],
write = []
/**
* @openapi
* /tables/{tableId}/rows/search:
* post:
* summary: Used to search for rows within a table.
* tags:
* - rows
* parameters:
* - $ref: '#/components/parameters/tableId'
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* query:
* type: object
* properties:
* string:
* type: object
* example:
* columnName1: value
* columnName2: value
* description: A map of field name to the string to search for,
* this will look for rows that have a value starting with the
* string value.
* additionalProperties:
* type: string
* description: The value to search for in the column.
* fuzzy:
* type: object
* description: A fuzzy search, only supported by internal tables.
* range:
* type: object
* description: Searches within a range, the format of this must be
* columnName -> [low, high].
* example:
* columnName1: [10, 20]
* equal:
* type: object
* description: Searches for rows that have a column value that is
* exactly the value set.
* notEqual:
* type: object
* description: Searches for any row which does not contain the specified
* column value.
* empty:
* type: object
* description: Searches for rows which do not contain the specified column.
* The object should simply contain keys of the column names, these
* can map to any value.
* example:
* columnName1: ""
* notEmpty:
* type: object
* description: Searches for rows which have the specified column.
* oneOf:
* type: object
* description: Searches for rows which have a column value that is any
* of the specified values. The format of this must be columnName -> [value1, value2].
* paginate:
* type: boolean
* description: Enables pagination, by default this is disabled.
* bookmark:
* oneOf:
* - type: string
* - type: integer
* description: If retrieving another page, the bookmark from the previous request must be supplied.
* limit:
* type: integer
* description: The maximum number of rows to return, useful when paginating, for internal tables this
* will be limited to 1000, for SQL tables it will be 5000.
* sort:
* type: object
* description: A set of parameters describing the sort behaviour of the search.
* properties:
* order:
* type: string
* enum: [ascending, descending]
* description: The order of the sort, by default this is ascending.
* column:
* type: string
* description: The name of the column by which the rows will be sorted.
* type:
* type: string
* enum: [string, number]
* description: Defines whether the column should be treated as a string
* or as numbers when sorting.
* responses:
* 200:
* description: The response will contain an array of rows that match the search parameters.
* content:
* application/json:
* schema:
* type: object
* properties:
* rows:
* description: An array of rows, these will each contain an _id field which can be used
* to update or delete them.
* type: array
* items:
* type: object
* bookmark:
* oneOf:
* - type: string
* - type: integer
* description: If pagination in use, this should be provided.
* hasNextPage:
* description: If pagination in use, this will determine if there is another page to fetch.
* type: boolean
* examples:
* search:
* $ref: '#/components/examples/rows'
*/
read.push(
new Endpoint(
"post",
"/tables/:tableId/rows/search",
controller.search
).addMiddleware(externalSearchValidator())
)
/**
* @openapi
* /tables/{tableId}/rows:
@ -247,4 +123,132 @@ write.push(
*/
read.push(new Endpoint("get", "/tables/:tableId/rows/:rowId", controller.read))
/**
* @openapi
* /tables/{tableId}/rows/search:
* post:
* summary: Used to search for rows within a table.
* tags:
* - rows
* parameters:
* - $ref: '#/components/parameters/tableId'
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - query
* properties:
* query:
* type: object
* properties:
* string:
* type: object
* example:
* columnName1: value
* columnName2: value
* description: A map of field name to the string to search for,
* this will look for rows that have a value starting with the
* string value.
* additionalProperties:
* type: string
* description: The value to search for in the column.
* fuzzy:
* type: object
* description: A fuzzy search, only supported by internal tables.
* range:
* type: object
* description: Searches within a range, the format of this must be
* columnName -> [low, high].
* example:
* columnName1: [10, 20]
* equal:
* type: object
* description: Searches for rows that have a column value that is
* exactly the value set.
* notEqual:
* type: object
* description: Searches for any row which does not contain the specified
* column value.
* empty:
* type: object
* description: Searches for rows which do not contain the specified column.
* The object should simply contain keys of the column names, these
* can map to any value.
* example:
* columnName1: ""
* notEmpty:
* type: object
* description: Searches for rows which have the specified column.
* oneOf:
* type: object
* description: Searches for rows which have a column value that is any
* of the specified values. The format of this must be columnName -> [value1, value2].
* paginate:
* type: boolean
* description: Enables pagination, by default this is disabled.
* bookmark:
* oneOf:
* - type: string
* - type: integer
* description: If retrieving another page, the bookmark from the previous request must be supplied.
* limit:
* type: integer
* description: The maximum number of rows to return, useful when paginating, for internal tables this
* will be limited to 1000, for SQL tables it will be 5000.
* sort:
* type: object
* description: A set of parameters describing the sort behaviour of the search.
* properties:
* order:
* type: string
* enum: [ascending, descending]
* description: The order of the sort, by default this is ascending.
* column:
* type: string
* description: The name of the column by which the rows will be sorted.
* type:
* type: string
* enum: [string, number]
* description: Defines whether the column should be treated as a string
* or as numbers when sorting.
* responses:
* 200:
* description: The response will contain an array of rows that match the search parameters.
* content:
* application/json:
* schema:
* type: object
* required:
* - rows
* properties:
* rows:
* description: An array of rows, these will each contain an _id field which can be used
* to update or delete them.
* type: array
* items:
* type: object
* bookmark:
* oneOf:
* - type: string
* - type: integer
* description: If pagination in use, this should be provided.
* hasNextPage:
* description: If pagination in use, this will determine if there is another page to fetch.
* type: boolean
* examples:
* search:
* $ref: '#/components/examples/rows'
*/
read.push(
new Endpoint(
"post",
"/tables/:tableId/rows/search",
controller.search
).addMiddleware(externalSearchValidator())
)
export default { read, write }

View File

@ -5,43 +5,6 @@ import { tableValidator, nameValidator } from "../utils/validators"
const read = [],
write = []
/**
* @openapi
* /tables/search:
* post:
* summary: Search internal and external tables based on their name.
* tags:
* - tables
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the found tables, based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* properties:
* applications:
* type: array
* items:
* $ref: '#/components/schemas/table'
* examples:
* tables:
* $ref: '#/components/examples/tables'
*/
read.push(
new Endpoint("post", "/tables/search", controller.search).addMiddleware(
nameValidator()
)
)
/**
* @openapi
* /tables:
@ -158,4 +121,43 @@ write.push(new Endpoint("delete", "/tables/:tableId", controller.destroy))
*/
read.push(new Endpoint("get", "/tables/:tableId", controller.read))
/**
* @openapi
* /tables/search:
* post:
* summary: Search internal and external tables based on their name.
* tags:
* - tables
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the found tables, based on the search parameters.
* content:
* application/json:
* schema:
* type: object
* required:
* - tables
* properties:
* tables:
* type: array
* items:
* $ref: '#/components/schemas/table'
* examples:
* tables:
* $ref: '#/components/examples/tables'
*/
read.push(
new Endpoint("post", "/tables/search", controller.search).addMiddleware(
nameValidator()
)
)
export default { read, write }

View File

@ -0,0 +1,74 @@
const jestOpenAPI = require("jest-openapi").default
const generateSchema = require("../../../../../specs/generate")
const setup = require("../../tests/utilities")
const { checkSlashesInUrl } = require("../../../../utilities")
const yamlPath = generateSchema()
jestOpenAPI(yamlPath)
let request = setup.getRequest()
let config = setup.getConfig()
let apiKey, table
beforeAll(async () => {
await config.init()
table = await config.updateTable()
apiKey = await config.generateApiKey()
})
afterAll(setup.afterAll)
async function makeRequest(method, endpoint, body, appId) {
const extraHeaders = {
"x-budibase-api-key": apiKey,
"x-budibase-app-id": appId ? appId : config.getAppId(),
}
const req = request
[method](checkSlashesInUrl(`/api/public/v1/${endpoint}`))
.set(config.defaultHeaders(extraHeaders))
if (body) {
req.send(body)
}
const res = await req.expect("Content-Type", /json/).expect(200)
expect(res.body).toBeDefined()
return res
}
describe("check the applications endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/applications/search")
expect(res).toSatisfyApiSpec()
})
})
describe("check the tables endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/tables/search")
expect(res).toSatisfyApiSpec()
})
})
describe("check the rows endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", `/tables/${table._id}/rows/search`, {
query: {
},
})
expect(res).toSatisfyApiSpec()
})
})
describe("check the users endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/users/search")
expect(res).toSatisfyApiSpec()
})
})
describe("check the queries endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/queries/search")
expect(res).toSatisfyApiSpec()
})
})

View File

@ -5,40 +5,6 @@ import { nameValidator } from "../utils/validators"
const read = [],
write = []
/**
* @openapi
* /users/search:
* post:
* summary: Search for a user based on their email/username.
* tags:
* - users
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the found users based on search parameters.
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/user'
* examples:
* users:
* $ref: '#/components/examples/users'
*/
read.push(
new Endpoint("post", "/users/search", controller.search).addMiddleware(
nameValidator()
)
)
/**
* @openapi
* /users:
@ -142,4 +108,43 @@ write.push(new Endpoint("delete", "/users/:userId", controller.destroy))
*/
read.push(new Endpoint("get", "/users/:userId", controller.read))
/**
* @openapi
* /users/search:
* post:
* summary: Search for a user based on their email/username.
* tags:
* - users
* parameters:
* - $ref: '#/components/parameters/appId'
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/nameSearch'
* responses:
* 200:
* description: Returns the found users based on search parameters.
* content:
* application/json:
* schema:
* type: object
* required:
* - users
* properties:
* users:
* type: array
* items:
* $ref: '#/components/schemas/user'
* examples:
* users:
* $ref: '#/components/examples/users'
*/
read.push(
new Endpoint("post", "/users/search", controller.search).addMiddleware(
nameValidator()
)
)
export default { read, write }

View File

@ -25,6 +25,8 @@ const { createASession } = require("@budibase/backend-core/sessions")
const { user: userCache } = require("@budibase/backend-core/cache")
const newid = require("../../db/newid")
const context = require("@budibase/backend-core/context")
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
const { encrypt } = require("@budibase/backend-core/encryption")
const GLOBAL_USER_ID = "us_uuid1"
const EMAIL = "babs@babs.com"
@ -83,6 +85,20 @@ class TestConfiguration {
}
}
async generateApiKey(userId = GLOBAL_USER_ID) {
const db = getGlobalDB(TENANT_ID)
const id = generateDevInfoID(userId)
let devInfo
try {
devInfo = await db.get(id)
} catch (err) {
devInfo = { _id: id, userId }
}
devInfo.apiKey = encrypt(`${TENANT_ID}${SEPARATOR}${newid()}`)
await db.put(devInfo)
return devInfo.apiKey
}
async globalUser({
id = GLOBAL_USER_ID,
builder = true,
@ -135,7 +151,7 @@ class TestConfiguration {
cleanup(this.allApps.map(app => app.appId))
}
defaultHeaders() {
defaultHeaders(extras = {}) {
const auth = {
userId: GLOBAL_USER_ID,
sessionId: "sessionid",
@ -154,6 +170,7 @@ class TestConfiguration {
`${Cookies.CurrentApp}=${appToken}`,
],
[Headers.CSRF_TOKEN]: CSRF_TOKEN,
...extras,
}
if (this.appId) {
headers[Headers.APP_ID] = this.appId

File diff suppressed because it is too large Load Diff