Merge branch 'master' into BUDI-8270/validation-for-search-api
# Conflicts: # packages/server/package.json # packages/server/src/api/controllers/row/index.ts # packages/server/src/api/routes/tests/search.spec.ts # yarn.lock
This commit is contained in:
commit
9c460424a9
|
@ -114,9 +114,11 @@ jobs:
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
if ${{ env.ONLY_AFFECTED_TASKS }}; then
|
if ${{ env.ONLY_AFFECTED_TASKS }}; then
|
||||||
yarn test -- --ignore=@budibase/worker --ignore=@budibase/server --no-prefix --since=${{ env.NX_BASE_BRANCH }} -- --verbose --reporters=default --reporters=github-actions
|
yarn test -- --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/builder --no-prefix --since=${{ env.NX_BASE_BRANCH }} -- --verbose --reporters=default --reporters=github-actions
|
||||||
|
yarn test -- --scope=@budibase/builder --since=${{ env.NX_BASE_BRANCH }}
|
||||||
else
|
else
|
||||||
yarn test -- --ignore=@budibase/worker --ignore=@budibase/server --no-prefix -- --verbose --reporters=default --reporters=github-actions
|
yarn test -- --ignore=@budibase/worker --ignore=@budibase/server --ignore=@budibase/builder --no-prefix -- --verbose --reporters=default --reporters=github-actions
|
||||||
|
yarn test -- --scope=@budibase/builder --no-prefix
|
||||||
fi
|
fi
|
||||||
|
|
||||||
test-worker:
|
test-worker:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "3.2.9",
|
"version": "3.2.10",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"concurrency": 20,
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
"semver": "7.5.3",
|
"semver": "7.5.3",
|
||||||
"http-cache-semantics": "4.1.1",
|
"http-cache-semantics": "4.1.1",
|
||||||
"msgpackr": "1.10.1",
|
"msgpackr": "1.10.1",
|
||||||
"axios": "1.6.3",
|
"axios": "1.7.7",
|
||||||
"xml2js": "0.6.2",
|
"xml2js": "0.6.2",
|
||||||
"unset-value": "2.0.1",
|
"unset-value": "2.0.1",
|
||||||
"passport": "0.6.0",
|
"passport": "0.6.0",
|
||||||
|
@ -119,6 +119,5 @@
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0 <21.0.0"
|
"node": ">=20.0.0 <21.0.0"
|
||||||
},
|
}
|
||||||
"dependencies": {}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,14 +33,17 @@
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.11",
|
"@budibase/pouchdb-replication-stream": "1.2.11",
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
|
"@techpass/passport-openidconnect": "0.3.3",
|
||||||
"aws-cloudfront-sign": "3.0.2",
|
"aws-cloudfront-sign": "3.0.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1692.0",
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bull": "4.10.1",
|
"bull": "4.10.1",
|
||||||
"correlation-id": "4.0.0",
|
"correlation-id": "4.0.0",
|
||||||
"dd-trace": "5.2.0",
|
"dd-trace": "5.23.0",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
|
"google-auth-library": "^8.0.1",
|
||||||
|
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"jsonwebtoken": "9.0.2",
|
"jsonwebtoken": "9.0.2",
|
||||||
|
@ -55,17 +58,14 @@
|
||||||
"pino": "8.11.0",
|
"pino": "8.11.0",
|
||||||
"pino-http": "8.3.3",
|
"pino-http": "8.3.3",
|
||||||
"posthog-node": "4.0.1",
|
"posthog-node": "4.0.1",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "9.0.0",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "9.0.0",
|
||||||
"redlock": "4.2.0",
|
"redlock": "4.2.0",
|
||||||
"rotating-file-stream": "3.1.0",
|
"rotating-file-stream": "3.1.0",
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"tar-fs": "2.1.1",
|
"tar-fs": "2.1.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2"
|
||||||
"@techpass/passport-openidconnect": "0.3.3",
|
|
||||||
"google-auth-library": "^8.0.1",
|
|
||||||
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
"@types/lodash": "4.14.200",
|
"@types/lodash": "4.14.200",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
"@types/pouchdb": "6.4.0",
|
"@types/pouchdb": "6.4.2",
|
||||||
"@types/redlock": "4.0.7",
|
"@types/redlock": "4.0.7",
|
||||||
"@types/semver": "7.3.7",
|
"@types/semver": "7.3.7",
|
||||||
"@types/tar-fs": "2.0.1",
|
"@types/tar-fs": "2.0.1",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit bfeece324a03a3a5f25137bf3f8c66d5ed6103d8
|
Subproject commit 4facf6a44ee52a405794845f71584168b9db652c
|
|
@ -63,13 +63,13 @@
|
||||||
"@bull-board/koa": "5.10.2",
|
"@bull-board/koa": "5.10.2",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@google-cloud/firestore": "7.8.0",
|
"@google-cloud/firestore": "7.8.0",
|
||||||
"@koa/router": "8.0.8",
|
"@koa/router": "13.1.0",
|
||||||
"@socket.io/redis-adapter": "^8.2.1",
|
"@socket.io/redis-adapter": "^8.2.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
"airtable": "0.12.2",
|
"airtable": "0.12.2",
|
||||||
"arangojs": "7.2.0",
|
"arangojs": "7.2.0",
|
||||||
"archiver": "7.0.1",
|
"archiver": "7.0.1",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1692.0",
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bson": "^6.9.0",
|
"bson": "^6.9.0",
|
||||||
|
@ -80,8 +80,8 @@
|
||||||
"cookies": "0.8.0",
|
"cookies": "0.8.0",
|
||||||
"csvtojson": "2.0.10",
|
"csvtojson": "2.0.10",
|
||||||
"curlconverter": "3.21.0",
|
"curlconverter": "3.21.0",
|
||||||
|
"dd-trace": "5.23.0",
|
||||||
"dayjs": "^1.10.8",
|
"dayjs": "^1.10.8",
|
||||||
"dd-trace": "5.2.0",
|
|
||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
"global-agent": "3.0.0",
|
"global-agent": "3.0.0",
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5",
|
"google-spreadsheet": "npm:@budibase/google-spreadsheet@4.1.5",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"isolated-vm": "^4.7.2",
|
"isolated-vm": "^4.7.2",
|
||||||
"jimp": "0.22.12",
|
"jimp": "1.1.4",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonschema": "1.4.0",
|
"jsonschema": "1.4.0",
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"memorystream": "0.3.1",
|
"memorystream": "0.3.1",
|
||||||
"mongodb": "6.7.0",
|
"mongodb": "6.7.0",
|
||||||
"mssql": "10.0.1",
|
"mssql": "11.0.1",
|
||||||
"mysql2": "3.9.8",
|
"mysql2": "3.9.8",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"object-sizeof": "2.6.1",
|
"object-sizeof": "2.6.1",
|
||||||
|
@ -112,15 +112,15 @@
|
||||||
"openapi-types": "9.3.1",
|
"openapi-types": "9.3.1",
|
||||||
"oracledb": "6.5.1",
|
"oracledb": "6.5.1",
|
||||||
"pg": "8.10.0",
|
"pg": "8.10.0",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "9.0.0",
|
||||||
"pouchdb-all-dbs": "1.1.1",
|
"pouchdb-all-dbs": "1.1.1",
|
||||||
"pouchdb-find": "7.2.2",
|
"pouchdb-find": "9.0.0",
|
||||||
"redis": "4",
|
"redis": "4",
|
||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"serialize-error": "^7.0.1",
|
"serialize-error": "^7.0.1",
|
||||||
"server-destroy": "1.0.1",
|
"server-destroy": "1.0.1",
|
||||||
"snowflake-promise": "^4.5.0",
|
"snowflake-sdk": "^1.15.0",
|
||||||
"socket.io": "4.7.5",
|
"socket.io": "4.8.1",
|
||||||
"svelte": "^4.2.10",
|
"svelte": "^4.2.10",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
"tmp": "0.2.3",
|
"tmp": "0.2.3",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"validate.js": "0.13.1",
|
"validate.js": "0.13.1",
|
||||||
"worker-farm": "1.7.0",
|
"worker-farm": "1.7.0",
|
||||||
"xml2js": "0.5.0",
|
"xml2js": "0.6.2",
|
||||||
"zod-validation-error": "^3.4.0"
|
"zod-validation-error": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -142,13 +142,14 @@
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa-send": "^4.1.6",
|
"@types/koa-send": "^4.1.6",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "12.0.4",
|
||||||
"@types/lodash": "4.14.200",
|
"@types/lodash": "4.14.200",
|
||||||
"@types/mssql": "9.1.4",
|
"@types/mssql": "9.1.5",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
"@types/oracledb": "6.5.1",
|
"@types/oracledb": "6.5.1",
|
||||||
"@types/pg": "8.6.6",
|
"@types/pg": "8.6.6",
|
||||||
|
"@types/pouchdb": "6.4.2",
|
||||||
"@types/server-destroy": "1.0.1",
|
"@types/server-destroy": "1.0.1",
|
||||||
"@types/supertest": "2.0.14",
|
"@types/supertest": "2.0.14",
|
||||||
"@types/tar": "6.1.5",
|
"@types/tar": "6.1.5",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { URL } from "url"
|
||||||
|
|
||||||
const curlconverter = require("curlconverter")
|
const curlconverter = require("curlconverter")
|
||||||
|
|
||||||
const parseCurl = (data: string): any => {
|
const parseCurl = (data: string): Promise<any> => {
|
||||||
const curlJson = curlconverter.toJsonString(data)
|
const curlJson = curlconverter.toJsonString(data)
|
||||||
return JSON.parse(curlJson)
|
return JSON.parse(curlJson)
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,7 @@ export class Curl extends ImportSource {
|
||||||
|
|
||||||
isSupported = async (data: string): Promise<boolean> => {
|
isSupported = async (data: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const curl = parseCurl(data)
|
this.curl = parseCurl(data)
|
||||||
this.curl = curl
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,9 +164,12 @@ describe("/datasources", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({
|
||||||
{ name: "%s", exclude: [DatabaseName.MONGODB, DatabaseName.SQS] },
|
exclude: [DatabaseName.MONGODB, DatabaseName.SQS],
|
||||||
({ config, dsProvider }) => {
|
})
|
||||||
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)("$dbName", ({ config, dsProvider }) => {
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
let rawDatasource: Datasource
|
let rawDatasource: Datasource
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
@ -492,5 +495,5 @@ datasourceDescribe(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
|
@ -14,8 +14,13 @@ import { events } from "@budibase/backend-core"
|
||||||
import { Knex } from "knex"
|
import { Knex } from "knex"
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({
|
||||||
{ name: "queries (%s)", exclude: [DatabaseName.MONGODB, DatabaseName.SQS] },
|
exclude: [DatabaseName.MONGODB, DatabaseName.SQS],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"queries ($dbName)",
|
||||||
({ config, dsProvider, isOracle, isMSSQL, isPostgres }) => {
|
({ config, dsProvider, isOracle, isMSSQL, isPostgres }) => {
|
||||||
let rawDatasource: Datasource
|
let rawDatasource: Datasource
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
|
@ -946,3 +951,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -9,8 +9,11 @@ import { generator } from "@budibase/backend-core/tests"
|
||||||
const expectValidId = expect.stringMatching(/^\w{24}$/)
|
const expectValidId = expect.stringMatching(/^\w{24}$/)
|
||||||
const expectValidBsonObjectId = expect.any(BSON.ObjectId)
|
const expectValidBsonObjectId = expect.any(BSON.ObjectId)
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ only: [DatabaseName.MONGODB] })
|
||||||
{ name: "/queries", only: [DatabaseName.MONGODB] },
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"/queries ($dbName)",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let collection: string
|
let collection: string
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
|
@ -715,3 +718,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -85,8 +85,11 @@ function encodeJS(binding: string) {
|
||||||
return `{{ js "${Buffer.from(binding).toString("base64")}"}}`
|
return `{{ js "${Buffer.from(binding).toString("base64")}"}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
{ name: "/rows (%s)", exclude: [DatabaseName.MONGODB] },
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"/rows ($dbName)",
|
||||||
({ config, dsProvider, isInternal, isMSSQL, isOracle }) => {
|
({ config, dsProvider, isInternal, isMSSQL, isOracle }) => {
|
||||||
let table: Table
|
let table: Table
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
|
@ -338,7 +341,9 @@ datasourceDescribe(
|
||||||
await new Promise(r => setTimeout(r, Math.random() * 50))
|
await new Promise(r => setTimeout(r, Math.random() * 50))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(`Failed to create row after ${attempts} attempts`)
|
throw new Error(
|
||||||
|
`Failed to create row after ${attempts} attempts`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1495,7 +1500,9 @@ datasourceDescribe(
|
||||||
it("should return no errors on valid row", async () => {
|
it("should return no errors on valid row", async () => {
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
|
|
||||||
const res = await config.api.row.validate(table._id!, { name: "ivan" })
|
const res = await config.api.row.validate(table._id!, {
|
||||||
|
name: "ivan",
|
||||||
|
})
|
||||||
|
|
||||||
expect(res.valid).toBe(true)
|
expect(res.valid).toBe(true)
|
||||||
expect(Object.keys(res.errors)).toEqual([])
|
expect(Object.keys(res.errors)).toEqual([])
|
||||||
|
@ -2244,7 +2251,10 @@ datasourceDescribe(
|
||||||
const table = await config.api.table.save(tableRequest)
|
const table = await config.api.table.save(tableRequest)
|
||||||
const toCreate = generator
|
const toCreate = generator
|
||||||
.unique(() => generator.integer({ min: 0, max: 10000 }), 10)
|
.unique(() => generator.integer({ min: 0, max: 10000 }), 10)
|
||||||
.map(number => ({ number, string: generator.word({ length: 30 }) }))
|
.map(number => ({
|
||||||
|
number,
|
||||||
|
string: generator.word({ length: 30 }),
|
||||||
|
}))
|
||||||
|
|
||||||
const rows = await Promise.all(
|
const rows = await Promise.all(
|
||||||
toCreate.map(d => config.api.row.save(table._id!, d))
|
toCreate.map(d => config.api.row.save(table._id!, d))
|
||||||
|
@ -3019,7 +3029,10 @@ datasourceDescribe(
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
["from original saved row", (row: Row) => row],
|
["from original saved row", (row: Row) => row],
|
||||||
["from updated row", (row: Row) => config.api.row.save(viewId, row)],
|
[
|
||||||
|
"from updated row",
|
||||||
|
(row: Row) => config.api.row.save(viewId, row),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
it.each(testScenarios)(
|
it.each(testScenarios)(
|
||||||
|
@ -3243,7 +3256,10 @@ datasourceDescribe(
|
||||||
|
|
||||||
async function updateFormulaColumn(
|
async function updateFormulaColumn(
|
||||||
formula: string,
|
formula: string,
|
||||||
opts?: { responseType?: FormulaResponseType; formulaType?: FormulaType }
|
opts?: {
|
||||||
|
responseType?: FormulaResponseType
|
||||||
|
formulaType?: FormulaType
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
table = await config.api.table.save({
|
table = await config.api.table.save({
|
||||||
...table,
|
...table,
|
||||||
|
@ -3481,5 +3497,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
// todo: remove me
|
|
||||||
|
|
|
@ -977,8 +977,13 @@ describe("/rowsActions", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({
|
||||||
{ name: "row actions (%s)", only: [DatabaseName.SQS, DatabaseName.POSTGRES] },
|
only: [DatabaseName.SQS, DatabaseName.POSTGRES],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"row actions ($dbName)",
|
||||||
({ config, dsProvider, isInternal }) => {
|
({ config, dsProvider, isInternal }) => {
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
|
|
||||||
|
@ -1037,3 +1042,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -60,11 +60,11 @@ jest.mock("@budibase/pro", () => ({
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
{
|
|
||||||
name: "search (%s)",
|
if (descriptions.length) {
|
||||||
exclude: [DatabaseName.MONGODB],
|
describe.each(descriptions)(
|
||||||
},
|
"search ($dbName)",
|
||||||
({ config, dsProvider, isInternal, isOracle, isSql }) => {
|
({ config, dsProvider, isInternal, isOracle, isSql }) => {
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
let client: Knex | undefined
|
let client: Knex | undefined
|
||||||
|
@ -199,7 +199,9 @@ datasourceDescribe(
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
describe.each(tableOrView)("from %s", (sourceType, createTableOrView) => {
|
describe.each(tableOrView)(
|
||||||
|
"from %s",
|
||||||
|
(sourceType, createTableOrView) => {
|
||||||
const isView = sourceType === "view"
|
const isView = sourceType === "view"
|
||||||
|
|
||||||
class SearchAssertion {
|
class SearchAssertion {
|
||||||
|
@ -207,23 +209,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
private async performSearch(): Promise<SearchResponse<Row>> {
|
private async performSearch(): Promise<SearchResponse<Row>> {
|
||||||
if (isInMemory) {
|
if (isInMemory) {
|
||||||
const query: RequiredKeys<Omit<RowSearchParams, "tableId">> = {
|
return dataFilters.search(_.cloneDeep(rows), {
|
||||||
sort: this.query.sort,
|
...this.query,
|
||||||
query: this.query.query || {},
|
})
|
||||||
paginate: this.query.paginate,
|
|
||||||
bookmark: this.query.bookmark ?? undefined,
|
|
||||||
limit: this.query.limit,
|
|
||||||
sortOrder: this.query.sortOrder,
|
|
||||||
sortType: this.query.sortType,
|
|
||||||
version: this.query.version,
|
|
||||||
disableEscaping: this.query.disableEscaping,
|
|
||||||
countRows: this.query.countRows,
|
|
||||||
viewId: undefined,
|
|
||||||
fields: undefined,
|
|
||||||
indexer: undefined,
|
|
||||||
rows: undefined,
|
|
||||||
}
|
|
||||||
return dataFilters.search(_.cloneDeep(rows), query)
|
|
||||||
} else {
|
} else {
|
||||||
return config.api.row.search(tableOrViewId, this.query)
|
return config.api.row.search(tableOrViewId, this.query)
|
||||||
}
|
}
|
||||||
|
@ -278,12 +266,16 @@ datasourceDescribe(
|
||||||
expectedRow: T,
|
expectedRow: T,
|
||||||
foundRows: T[]
|
foundRows: T[]
|
||||||
): NonNullable<T> {
|
): NonNullable<T> {
|
||||||
const row = foundRows.find(row => this.isMatch(expectedRow, row))
|
const row = foundRows.find(row =>
|
||||||
|
this.isMatch(expectedRow, row)
|
||||||
|
)
|
||||||
if (!row) {
|
if (!row) {
|
||||||
const fields = Object.keys(expectedRow)
|
const fields = Object.keys(expectedRow)
|
||||||
// To make the error message more readable, we only include the fields
|
// To make the error message more readable, we only include the fields
|
||||||
// that are present in the expected row.
|
// that are present in the expected row.
|
||||||
const searchedObjects = foundRows.map(row => _.pick(row, fields))
|
const searchedObjects = foundRows.map(row =>
|
||||||
|
_.pick(row, fields)
|
||||||
|
)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to find row:\n\n${JSON.stringify(
|
`Failed to find row:\n\n${JSON.stringify(
|
||||||
expectedRow,
|
expectedRow,
|
||||||
|
@ -331,7 +323,9 @@ datasourceDescribe(
|
||||||
expect([...foundRows]).toEqual(
|
expect([...foundRows]).toEqual(
|
||||||
expect.arrayContaining(
|
expect.arrayContaining(
|
||||||
expectedRows.map((expectedRow: any) =>
|
expectedRows.map((expectedRow: any) =>
|
||||||
expect.objectContaining(this.popRow(expectedRow, foundRows))
|
expect.objectContaining(
|
||||||
|
this.popRow(expectedRow, foundRows)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -359,7 +353,9 @@ datasourceDescribe(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asserts that the query doesn't return a property, e.g. pagination parameters.
|
// Asserts that the query doesn't return a property, e.g. pagination parameters.
|
||||||
async toNotHaveProperty(properties: (keyof SearchResponse<Row>)[]) {
|
async toNotHaveProperty(
|
||||||
|
properties: (keyof SearchResponse<Row>)[]
|
||||||
|
) {
|
||||||
const response = await this.performSearch()
|
const response = await this.performSearch()
|
||||||
const cloned = cloneDeep(response)
|
const cloned = cloneDeep(response)
|
||||||
for (let property of properties) {
|
for (let property of properties) {
|
||||||
|
@ -381,7 +377,9 @@ datasourceDescribe(
|
||||||
expect([...foundRows]).toEqual(
|
expect([...foundRows]).toEqual(
|
||||||
expect.arrayContaining(
|
expect.arrayContaining(
|
||||||
expectedRows.map((expectedRow: any) =>
|
expectedRows.map((expectedRow: any) =>
|
||||||
expect.objectContaining(this.popRow(expectedRow, foundRows))
|
expect.objectContaining(
|
||||||
|
this.popRow(expectedRow, foundRows)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -418,15 +416,15 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it("successfully finds true row", async () => {
|
it("successfully finds true row", async () => {
|
||||||
await expectQuery({ equal: { isTrue: true } }).toMatchExactly([
|
await expectQuery({ equal: { isTrue: true } }).toMatchExactly(
|
||||||
{ isTrue: true },
|
[{ isTrue: true }]
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully finds false row", async () => {
|
it("successfully finds false row", async () => {
|
||||||
await expectQuery({ equal: { isTrue: false } }).toMatchExactly([
|
await expectQuery({
|
||||||
{ isTrue: false },
|
equal: { isTrue: false },
|
||||||
])
|
}).toMatchExactly([{ isTrue: false }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -446,9 +444,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds true row", async () => {
|
it("successfully finds true row", async () => {
|
||||||
await expectQuery({ oneOf: { isTrue: [true] } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ isTrue: true }]
|
oneOf: { isTrue: [true] },
|
||||||
)
|
}).toContainExactly([{ isTrue: true }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully finds false row", async () => {
|
it("successfully finds false row", async () => {
|
||||||
|
@ -500,7 +498,10 @@ datasourceDescribe(
|
||||||
name: currentUser.firstName,
|
name: currentUser.firstName,
|
||||||
appointment: future.toISOString(),
|
appointment: future.toISOString(),
|
||||||
},
|
},
|
||||||
{ name: "serverDate", appointment: serverTime.toISOString() },
|
{
|
||||||
|
name: "serverDate",
|
||||||
|
appointment: serverTime.toISOString(),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "single user, session user",
|
name: "single user, session user",
|
||||||
single_user: currentUser,
|
single_user: currentUser,
|
||||||
|
@ -555,7 +556,10 @@ datasourceDescribe(
|
||||||
|
|
||||||
tableOrViewId = await createTableOrView({
|
tableOrViewId = await createTableOrView({
|
||||||
name: { name: "name", type: FieldType.STRING },
|
name: { name: "name", type: FieldType.STRING },
|
||||||
appointment: { name: "appointment", type: FieldType.DATETIME },
|
appointment: {
|
||||||
|
name: "appointment",
|
||||||
|
type: FieldType.DATETIME,
|
||||||
|
},
|
||||||
single_user: {
|
single_user: {
|
||||||
name: "single_user",
|
name: "single_user",
|
||||||
type: FieldType.BB_REFERENCE_SINGLE,
|
type: FieldType.BB_REFERENCE_SINGLE,
|
||||||
|
@ -601,7 +605,9 @@ datasourceDescribe(
|
||||||
it("should return all rows matching the session user firstname when logical operator used", async () => {
|
it("should return all rows matching the session user firstname when logical operator used", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
$and: {
|
$and: {
|
||||||
conditions: [{ equal: { name: "{{ [user].firstName }}" } }],
|
conditions: [
|
||||||
|
{ equal: { name: "{{ [user].firstName }}" } },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{
|
{
|
||||||
|
@ -625,7 +631,10 @@ datasourceDescribe(
|
||||||
name: config.getUser().firstName,
|
name: config.getUser().firstName,
|
||||||
appointment: future.toISOString(),
|
appointment: future.toISOString(),
|
||||||
},
|
},
|
||||||
{ name: "serverDate", appointment: serverTime.toISOString() },
|
{
|
||||||
|
name: "serverDate",
|
||||||
|
appointment: serverTime.toISOString(),
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -641,7 +650,10 @@ datasourceDescribe(
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
|
{ name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
|
||||||
{ name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
|
{ name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
|
||||||
{ name: "serverDate", appointment: serverTime.toISOString() },
|
{
|
||||||
|
name: "serverDate",
|
||||||
|
appointment: serverTime.toISOString(),
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -751,7 +763,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
it("should not match the session user id in a deprecated multi user field", async () => {
|
it("should not match the session user id in a deprecated multi user field", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
notContains: { deprecated_multi_user: ["{{ [user]._id }}"] },
|
notContains: {
|
||||||
|
deprecated_multi_user: ["{{ [user]._id }}"],
|
||||||
|
},
|
||||||
notEmpty: { deprecated_multi_user: true },
|
notEmpty: { deprecated_multi_user: true },
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{
|
{
|
||||||
|
@ -885,9 +899,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ equal: { name: "foo" } }).toContainExactly([
|
await expectQuery({
|
||||||
{ name: "foo" },
|
equal: { name: "foo" },
|
||||||
])
|
}).toContainExactly([{ name: "foo" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -912,27 +926,29 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("notEqual", () => {
|
describe("notEqual", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ notEqual: { name: "foo" } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ name: "bar" }]
|
notEqual: { name: "foo" },
|
||||||
)
|
}).toContainExactly([{ name: "bar" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ notEqual: { name: "bar" } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ name: "foo" }]
|
notEqual: { name: "bar" },
|
||||||
)
|
}).toContainExactly([{ name: "foo" }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ oneOf: { name: ["foo"] } }).toContainExactly([
|
await expectQuery({
|
||||||
{ name: "foo" },
|
oneOf: { name: ["foo"] },
|
||||||
])
|
}).toContainExactly([{ name: "foo" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ oneOf: { name: ["none"] } }).toFindNothing()
|
await expectQuery({
|
||||||
|
oneOf: { name: ["none"] },
|
||||||
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can have multiple values for same column", async () => {
|
it("can have multiple values for same column", async () => {
|
||||||
|
@ -980,9 +996,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("fuzzy", () => {
|
describe("fuzzy", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ fuzzy: { name: "oo" } }).toContainExactly([
|
await expectQuery({ fuzzy: { name: "oo" } }).toContainExactly(
|
||||||
{ name: "foo" },
|
[{ name: "foo" }]
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -992,19 +1008,21 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("string", () => {
|
describe("string", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ string: { name: "fo" } }).toContainExactly([
|
await expectQuery({
|
||||||
{ name: "foo" },
|
string: { name: "fo" },
|
||||||
])
|
}).toContainExactly([{ name: "foo" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ string: { name: "none" } }).toFindNothing()
|
await expectQuery({
|
||||||
|
string: { name: "none" },
|
||||||
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("is case-insensitive", async () => {
|
it("is case-insensitive", async () => {
|
||||||
await expectQuery({ string: { name: "FO" } }).toContainExactly([
|
await expectQuery({
|
||||||
{ name: "foo" },
|
string: { name: "FO" },
|
||||||
])
|
}).toContainExactly([{ name: "foo" }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1063,10 +1081,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("notEmpty", () => {
|
describe("notEmpty", () => {
|
||||||
it("finds all non-empty rows", async () => {
|
it("finds all non-empty rows", async () => {
|
||||||
await expectQuery({ notEmpty: { name: null } }).toContainExactly([
|
await expectQuery({
|
||||||
{ name: "foo" },
|
notEmpty: { name: null },
|
||||||
{ name: "bar" },
|
}).toContainExactly([{ name: "foo" }, { name: "bar" }])
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not be affected by when filter empty behaviour", async () => {
|
it("should not be affected by when filter empty behaviour", async () => {
|
||||||
|
@ -1182,9 +1199,9 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ notEqual: { age: 10 } }).toContainExactly([
|
await expectQuery({ notEqual: { age: 10 } }).toContainExactly(
|
||||||
{ age: 1 },
|
[{ age: 1 }]
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1332,9 +1349,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ equal: { dob: JAN_1ST } }).toContainExactly([
|
await expectQuery({
|
||||||
{ dob: JAN_1ST },
|
equal: { dob: JAN_1ST },
|
||||||
])
|
}).toContainExactly([{ dob: JAN_1ST }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -1358,13 +1375,15 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ dob: JAN_1ST }]
|
oneOf: { dob: [JAN_1ST] },
|
||||||
)
|
}).toContainExactly([{ dob: JAN_1ST }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing()
|
await expectQuery({
|
||||||
|
oneOf: { dob: [JAN_2ND] },
|
||||||
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1396,7 +1415,10 @@ datasourceDescribe(
|
||||||
it("greater than equal to", async () => {
|
it("greater than equal to", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
range: {
|
range: {
|
||||||
dob: { low: JAN_10TH, high: MAX_VALID_DATE.toISOString() },
|
dob: {
|
||||||
|
low: JAN_10TH,
|
||||||
|
high: MAX_VALID_DATE.toISOString(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).toContainExactly([{ dob: JAN_10TH }])
|
}).toContainExactly([{ dob: JAN_10TH }])
|
||||||
})
|
})
|
||||||
|
@ -1499,9 +1521,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ equal: { time: T_1000 } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ time: "10:00:00" }]
|
equal: { time: T_1000 },
|
||||||
)
|
}).toContainExactly([{ time: "10:00:00" }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -1707,7 +1729,9 @@ datasourceDescribe(
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
oneOf: { ai: ["Mock LLM Response", "Other LLM Response"] },
|
oneOf: {
|
||||||
|
ai: ["Mock LLM Response", "Other LLM Response"],
|
||||||
|
},
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ product: "Big Mac" },
|
{ product: "Big Mac" },
|
||||||
{ product: "McCrispy" },
|
{ product: "McCrispy" },
|
||||||
|
@ -1760,9 +1784,12 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("finds all with empty list", async () => {
|
it("finds all with empty list", async () => {
|
||||||
await expectQuery({ contains: { numbers: [] } }).toContainExactly(
|
await expectQuery({
|
||||||
[{ numbers: ["one", "two"] }, { numbers: ["three"] }]
|
contains: { numbers: [] },
|
||||||
)
|
}).toContainExactly([
|
||||||
|
{ numbers: ["one", "two"] },
|
||||||
|
{ numbers: ["three"] },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1832,14 +1859,18 @@ datasourceDescribe(
|
||||||
tableOrViewId = await createTableOrView({
|
tableOrViewId = await createTableOrView({
|
||||||
num: { name: "num", type: FieldType.BIGINT },
|
num: { name: "num", type: FieldType.BIGINT },
|
||||||
})
|
})
|
||||||
await createRows([{ num: SMALL }, { num: MEDIUM }, { num: BIG }])
|
await createRows([
|
||||||
|
{ num: SMALL },
|
||||||
|
{ num: MEDIUM },
|
||||||
|
{ num: BIG },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ equal: { num: SMALL } }).toContainExactly([
|
await expectQuery({ equal: { num: SMALL } }).toContainExactly(
|
||||||
{ num: SMALL },
|
[{ num: SMALL }]
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully finds a big value", async () => {
|
it("successfully finds a big value", async () => {
|
||||||
|
@ -1855,26 +1886,23 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("notEqual", () => {
|
describe("notEqual", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ notEqual: { num: SMALL } }).toContainExactly([
|
await expectQuery({
|
||||||
{ num: MEDIUM },
|
notEqual: { num: SMALL },
|
||||||
{ num: BIG },
|
}).toContainExactly([{ num: MEDIUM }, { num: BIG }])
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ notEqual: { num: 10 } }).toContainExactly([
|
await expectQuery({ notEqual: { num: 10 } }).toContainExactly(
|
||||||
{ num: SMALL },
|
[{ num: SMALL }, { num: MEDIUM }, { num: BIG }]
|
||||||
{ num: MEDIUM },
|
)
|
||||||
{ num: BIG },
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ oneOf: { num: [SMALL] } }).toContainExactly([
|
await expectQuery({
|
||||||
{ num: SMALL },
|
oneOf: { num: [SMALL] },
|
||||||
])
|
}).toContainExactly([{ num: SMALL }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("successfully finds all rows", async () => {
|
it("successfully finds all rows", async () => {
|
||||||
|
@ -1959,7 +1987,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("not equal", () => {
|
describe("not equal", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ notEqual: { auto: 1 } }).toContainExactly([
|
await expectQuery({
|
||||||
|
notEqual: { auto: 1 },
|
||||||
|
}).toContainExactly([
|
||||||
{ auto: 2 },
|
{ auto: 2 },
|
||||||
{ auto: 3 },
|
{ auto: 3 },
|
||||||
{ auto: 4 },
|
{ auto: 4 },
|
||||||
|
@ -1973,7 +2003,9 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ notEqual: { auto: 0 } }).toContainExactly([
|
await expectQuery({
|
||||||
|
notEqual: { auto: 0 },
|
||||||
|
}).toContainExactly([
|
||||||
{ auto: 1 },
|
{ auto: 1 },
|
||||||
{ auto: 2 },
|
{ auto: 2 },
|
||||||
{ auto: 3 },
|
{ auto: 3 },
|
||||||
|
@ -1990,9 +2022,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("oneOf", () => {
|
describe("oneOf", () => {
|
||||||
it("successfully finds a row", async () => {
|
it("successfully finds a row", async () => {
|
||||||
await expectQuery({ oneOf: { auto: [1] } }).toContainExactly([
|
await expectQuery({
|
||||||
{ auto: 1 },
|
oneOf: { auto: [1] },
|
||||||
])
|
}).toContainExactly([{ auto: 1 }])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -2096,13 +2128,16 @@ datasourceDescribe(
|
||||||
hasNextPage: boolean | undefined = true,
|
hasNextPage: boolean | undefined = true,
|
||||||
rowCount: number = 0
|
rowCount: number = 0
|
||||||
do {
|
do {
|
||||||
const response = await config.api.row.search(tableOrViewId, {
|
const response = await config.api.row.search(
|
||||||
|
tableOrViewId,
|
||||||
|
{
|
||||||
tableId: tableOrViewId,
|
tableId: tableOrViewId,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
paginate: true,
|
paginate: true,
|
||||||
query: {},
|
query: {},
|
||||||
bookmark,
|
bookmark,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
bookmark = response.bookmark
|
bookmark = response.bookmark
|
||||||
hasNextPage = response.hasNextPage
|
hasNextPage = response.hasNextPage
|
||||||
expect(response.rows.length).toEqual(1)
|
expect(response.rows.length).toEqual(1)
|
||||||
|
@ -2120,13 +2155,16 @@ datasourceDescribe(
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
while (true) {
|
while (true) {
|
||||||
const response = await config.api.row.search(tableOrViewId, {
|
const response = await config.api.row.search(
|
||||||
|
tableOrViewId,
|
||||||
|
{
|
||||||
tableId: tableOrViewId,
|
tableId: tableOrViewId,
|
||||||
limit: 3,
|
limit: 3,
|
||||||
query: {},
|
query: {},
|
||||||
bookmark,
|
bookmark,
|
||||||
paginate: true,
|
paginate: true,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
rows.push(...response.rows)
|
rows.push(...response.rows)
|
||||||
|
|
||||||
|
@ -2159,7 +2197,9 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ equal: { "1:1:name": "none" } }).toFindNothing()
|
await expectQuery({
|
||||||
|
equal: { "1:1:name": "none" },
|
||||||
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2236,7 +2276,11 @@ datasourceDescribe(
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
await createRows([{ user: user1 }, { user: user2 }, { user: null }])
|
await createRows([
|
||||||
|
{ user: user1 },
|
||||||
|
{ user: user2 },
|
||||||
|
{ user: null },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("equal", () => {
|
describe("equal", () => {
|
||||||
|
@ -2247,7 +2291,9 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
await expectQuery({ equal: { user: "us_none" } }).toFindNothing()
|
await expectQuery({
|
||||||
|
equal: { user: "us_none" },
|
||||||
|
}).toFindNothing()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2285,15 +2331,17 @@ datasourceDescribe(
|
||||||
|
|
||||||
describe("empty", () => {
|
describe("empty", () => {
|
||||||
it("finds empty rows", async () => {
|
it("finds empty rows", async () => {
|
||||||
await expectQuery({ empty: { user: null } }).toContainExactly([
|
await expectQuery({ empty: { user: null } }).toContainExactly(
|
||||||
{},
|
[{}]
|
||||||
])
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("notEmpty", () => {
|
describe("notEmpty", () => {
|
||||||
it("finds non-empty rows", async () => {
|
it("finds non-empty rows", async () => {
|
||||||
await expectQuery({ notEmpty: { user: null } }).toContainExactly([
|
await expectQuery({
|
||||||
|
notEmpty: { user: null },
|
||||||
|
}).toContainExactly([
|
||||||
{ user: { _id: user1._id } },
|
{ user: { _id: user1._id } },
|
||||||
{ user: { _id: user2._id } },
|
{ user: { _id: user2._id } },
|
||||||
])
|
])
|
||||||
|
@ -2400,7 +2448,9 @@ datasourceDescribe(
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
equal: { number: 1 },
|
equal: { number: 1 },
|
||||||
contains: { users: [user1._id] },
|
contains: { users: [user1._id] },
|
||||||
}).toContainExactly([{ users: [{ _id: user1._id }], number: 1 }])
|
}).toContainExactly([
|
||||||
|
{ users: [{ _id: user1._id }], number: 1 },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("fails to find nonexistent row", async () => {
|
it("fails to find nonexistent row", async () => {
|
||||||
|
@ -2423,15 +2473,18 @@ datasourceDescribe(
|
||||||
let productCategoryTable: Table, productCatRows: Row[]
|
let productCategoryTable: Table, productCatRows: Row[]
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const { relatedTable, tableId } = await basicRelationshipTables(
|
const { relatedTable, tableId } =
|
||||||
relationshipType
|
await basicRelationshipTables(relationshipType)
|
||||||
)
|
|
||||||
tableOrViewId = tableId
|
tableOrViewId = tableId
|
||||||
productCategoryTable = relatedTable
|
productCategoryTable = relatedTable
|
||||||
|
|
||||||
productCatRows = await Promise.all([
|
productCatRows = await Promise.all([
|
||||||
config.api.row.save(productCategoryTable._id!, { name: "foo" }),
|
config.api.row.save(productCategoryTable._id!, {
|
||||||
config.api.row.save(productCategoryTable._id!, { name: "bar" }),
|
name: "foo",
|
||||||
|
}),
|
||||||
|
config.api.row.save(productCategoryTable._id!, {
|
||||||
|
name: "bar",
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -2454,7 +2507,10 @@ datasourceDescribe(
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
equal: { ["productCat.name"]: "foo" },
|
equal: { ["productCat.name"]: "foo" },
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ name: "foo", productCat: [{ _id: productCatRows[0]._id }] },
|
{
|
||||||
|
name: "foo",
|
||||||
|
productCat: [{ _id: productCatRows[0]._id }],
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2462,7 +2518,10 @@ datasourceDescribe(
|
||||||
await expectQuery({
|
await expectQuery({
|
||||||
equal: { [`${productCategoryTable.name}.name`]: "foo" },
|
equal: { [`${productCategoryTable.name}.name`]: "foo" },
|
||||||
}).toContainExactly([
|
}).toContainExactly([
|
||||||
{ name: "foo", productCat: [{ _id: productCatRows[0]._id }] },
|
{
|
||||||
|
name: "foo",
|
||||||
|
productCat: [{ _id: productCatRows[0]._id }],
|
||||||
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2473,7 +2532,10 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("logical filters", () => {
|
describe("logical filters", () => {
|
||||||
const logicalOperators = [LogicalOperator.AND, LogicalOperator.OR]
|
const logicalOperators = [
|
||||||
|
LogicalOperator.AND,
|
||||||
|
LogicalOperator.OR,
|
||||||
|
]
|
||||||
|
|
||||||
describe("$and", () => {
|
describe("$and", () => {
|
||||||
it("should allow single conditions", async () => {
|
it("should allow single conditions", async () => {
|
||||||
|
@ -2714,9 +2776,8 @@ datasourceDescribe(
|
||||||
RelationshipType.MANY_TO_MANY,
|
RelationshipType.MANY_TO_MANY,
|
||||||
])("big relations (%s)", relationshipType => {
|
])("big relations (%s)", relationshipType => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const { relatedTable, tableId } = await basicRelationshipTables(
|
const { relatedTable, tableId } =
|
||||||
relationshipType
|
await basicRelationshipTables(relationshipType)
|
||||||
)
|
|
||||||
tableOrViewId = tableId
|
tableOrViewId = tableId
|
||||||
const mainRow = await config.api.row.save(tableOrViewId, {
|
const mainRow = await config.api.row.save(tableOrViewId, {
|
||||||
name: "foo",
|
name: "foo",
|
||||||
|
@ -2730,12 +2791,15 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can only pull 10 related rows", async () => {
|
it("can only pull 10 related rows", async () => {
|
||||||
await withCoreEnv({ SQL_MAX_RELATED_ROWS: "10" }, async () => {
|
await withCoreEnv(
|
||||||
|
{ SQL_MAX_RELATED_ROWS: "10" },
|
||||||
|
async () => {
|
||||||
const response = await expectQuery({}).toContain([
|
const response = await expectQuery({}).toContain([
|
||||||
{ name: "foo" },
|
{ name: "foo" },
|
||||||
])
|
])
|
||||||
expect(response.rows[0].productCat).toBeArrayOfSize(10)
|
expect(response.rows[0].productCat).toBeArrayOfSize(10)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can pull max rows when env not set (defaults to 500)", async () => {
|
it("can pull max rows when env not set (defaults to 500)", async () => {
|
||||||
|
@ -2950,9 +3014,11 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.each(["data_name_test", "name_data_test", "name_test_data_"])(
|
describe.each([
|
||||||
"special (%s) case",
|
"data_name_test",
|
||||||
column => {
|
"name_data_test",
|
||||||
|
"name_test_data_",
|
||||||
|
])("special (%s) case", column => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
tableOrViewId = await createTableOrView({
|
tableOrViewId = await createTableOrView({
|
||||||
[column]: {
|
[column]: {
|
||||||
|
@ -2972,8 +3038,7 @@ datasourceDescribe(
|
||||||
},
|
},
|
||||||
}).toContainExactly([{ [column]: "a" }])
|
}).toContainExactly([{ [column]: "a" }])
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
isInternal &&
|
isInternal &&
|
||||||
describe("sample data", () => {
|
describe("sample data", () => {
|
||||||
|
@ -2995,10 +3060,22 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.each([
|
describe.each([
|
||||||
{ low: "2024-07-03T00:00:00.000Z", high: "9999-00-00T00:00:00.000Z" },
|
{
|
||||||
{ low: "2024-07-03T00:00:00.000Z", high: "9998-00-00T00:00:00.000Z" },
|
low: "2024-07-03T00:00:00.000Z",
|
||||||
{ low: "0000-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
high: "9999-00-00T00:00:00.000Z",
|
||||||
{ low: "0001-00-00T00:00:00.000Z", high: "2024-07-04T00:00:00.000Z" },
|
},
|
||||||
|
{
|
||||||
|
low: "2024-07-03T00:00:00.000Z",
|
||||||
|
high: "9998-00-00T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
low: "0000-00-00T00:00:00.000Z",
|
||||||
|
high: "2024-07-04T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
low: "0001-00-00T00:00:00.000Z",
|
||||||
|
high: "2024-07-04T00:00:00.000Z",
|
||||||
|
},
|
||||||
])("date special cases", ({ low, high }) => {
|
])("date special cases", ({ low, high }) => {
|
||||||
const earlyDate = "2024-07-03T10:00:00.000Z",
|
const earlyDate = "2024-07-03T10:00:00.000Z",
|
||||||
laterDate = "2024-07-03T11:00:00.000Z"
|
laterDate = "2024-07-03T11:00:00.000Z"
|
||||||
|
@ -3275,13 +3352,17 @@ datasourceDescribe(
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const toRelateTable = await config.api.table.get(toRelateTableId)
|
const toRelateTable = await config.api.table.get(
|
||||||
|
toRelateTableId
|
||||||
|
)
|
||||||
await config.api.table.save({
|
await config.api.table.save({
|
||||||
...toRelateTable,
|
...toRelateTable,
|
||||||
primaryDisplay: "link",
|
primaryDisplay: "link",
|
||||||
})
|
})
|
||||||
const relatedRows = await Promise.all([
|
const relatedRows = await Promise.all([
|
||||||
config.api.row.save(toRelateTable._id!, { name: "related" }),
|
config.api.row.save(toRelateTable._id!, {
|
||||||
|
name: "related",
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
await config.api.row.save(tableOrViewId, {
|
await config.api.row.save(tableOrViewId, {
|
||||||
name: "test",
|
name: "test",
|
||||||
|
@ -3660,7 +3741,9 @@ datasourceDescribe(
|
||||||
"'; SHUTDOWN --",
|
"'; SHUTDOWN --",
|
||||||
]
|
]
|
||||||
|
|
||||||
describe.each(badStrings)("bad string: %s", badStringTemplate => {
|
describe.each(badStrings)(
|
||||||
|
"bad string: %s",
|
||||||
|
badStringTemplate => {
|
||||||
// The SQL that knex generates when you try to use a double quote in a
|
// The SQL that knex generates when you try to use a double quote in a
|
||||||
// field name is always invalid and never works, so we skip it for these
|
// field name is always invalid and never works, so we skip it for these
|
||||||
// tests.
|
// tests.
|
||||||
|
@ -3680,12 +3763,17 @@ datasourceDescribe(
|
||||||
...table,
|
...table,
|
||||||
schema: {
|
schema: {
|
||||||
...table.schema,
|
...table.schema,
|
||||||
[badString]: { name: badString, type: FieldType.STRING },
|
[badString]: {
|
||||||
|
name: badString,
|
||||||
|
type: FieldType.STRING,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (docIds.isViewId(tableOrViewId)) {
|
if (docIds.isViewId(tableOrViewId)) {
|
||||||
const view = await config.api.viewV2.get(tableOrViewId)
|
const view = await config.api.viewV2.get(
|
||||||
|
tableOrViewId
|
||||||
|
)
|
||||||
await config.api.viewV2.update({
|
await config.api.viewV2.update({
|
||||||
...view,
|
...view,
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -3741,9 +3829,12 @@ datasourceDescribe(
|
||||||
await assertTableExists(table)
|
await assertTableExists(table)
|
||||||
await assertTableNumRows(table, 1)
|
await assertTableNumRows(table, 1)
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -38,8 +38,11 @@ import timekeeper from "timekeeper"
|
||||||
const { basicTable } = setup.structures
|
const { basicTable } = setup.structures
|
||||||
const ISO_REGEX_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
const ISO_REGEX_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
{ name: "/tables (%s)", exclude: [DatabaseName.MONGODB] },
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"/tables ($dbName)",
|
||||||
({ config, dsProvider, isInternal, isOracle }) => {
|
({ config, dsProvider, isInternal, isOracle }) => {
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
|
|
||||||
|
@ -332,7 +335,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
expect(updatedTable).toEqual(expect.objectContaining(expected))
|
expect(updatedTable).toEqual(expect.objectContaining(expected))
|
||||||
|
|
||||||
const persistedTable = await config.api.table.get(updatedTable._id!)
|
const persistedTable = await config.api.table.get(
|
||||||
|
updatedTable._id!
|
||||||
|
)
|
||||||
expected = {
|
expected = {
|
||||||
...table,
|
...table,
|
||||||
name: newName,
|
name: newName,
|
||||||
|
@ -561,8 +566,14 @@ datasourceDescribe(
|
||||||
await config.api.table.save(saveTableRequest, {
|
await config.api.table.save(saveTableRequest, {
|
||||||
status: 200,
|
status: 200,
|
||||||
})
|
})
|
||||||
saveTableRequest.schema.foo = { type: FieldType.STRING, name: "foo" }
|
saveTableRequest.schema.foo = {
|
||||||
saveTableRequest.schema.FOO = { type: FieldType.STRING, name: "FOO" }
|
type: FieldType.STRING,
|
||||||
|
name: "foo",
|
||||||
|
}
|
||||||
|
saveTableRequest.schema.FOO = {
|
||||||
|
type: FieldType.STRING,
|
||||||
|
name: "FOO",
|
||||||
|
}
|
||||||
|
|
||||||
await config.api.table.save(saveTableRequest, {
|
await config.api.table.save(saveTableRequest, {
|
||||||
status: 400,
|
status: 400,
|
||||||
|
@ -1180,10 +1191,12 @@ datasourceDescribe(
|
||||||
schema,
|
schema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const result = await config.api.table.validateExistingTableImport({
|
const result = await config.api.table.validateExistingTableImport(
|
||||||
|
{
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
rows,
|
rows,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1267,7 +1280,9 @@ datasourceDescribe(
|
||||||
|
|
||||||
isInternal &&
|
isInternal &&
|
||||||
it.each(
|
it.each(
|
||||||
isInternal ? PROTECTED_INTERNAL_COLUMNS : PROTECTED_EXTERNAL_COLUMNS
|
isInternal
|
||||||
|
? PROTECTED_INTERNAL_COLUMNS
|
||||||
|
: PROTECTED_EXTERNAL_COLUMNS
|
||||||
)(
|
)(
|
||||||
"don't allow protected names in the rows (%s)",
|
"don't allow protected names in the rows (%s)",
|
||||||
async columnName => {
|
async columnName => {
|
||||||
|
@ -1487,7 +1502,8 @@ datasourceDescribe(
|
||||||
schema: basicSchema,
|
schema: basicSchema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const result = await config.api.table.validateExistingTableImport({
|
const result = await config.api.table.validateExistingTableImport(
|
||||||
|
{
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
rows: [
|
rows: [
|
||||||
{
|
{
|
||||||
|
@ -1496,7 +1512,8 @@ datasourceDescribe(
|
||||||
name: generator.first(),
|
name: generator.first(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
allValid: true,
|
allValid: true,
|
||||||
|
@ -1513,3 +1530,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -44,8 +44,11 @@ import merge from "lodash/merge"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { db, roles, context } from "@budibase/backend-core"
|
import { db, roles, context } from "@budibase/backend-core"
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
{ name: "/v2/views (%s)", exclude: [DatabaseName.MONGODB] },
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"/v2/views ($dbName)",
|
||||||
({ config, isInternal, dsProvider }) => {
|
({ config, isInternal, dsProvider }) => {
|
||||||
let table: Table
|
let table: Table
|
||||||
let rawDatasource: Datasource | undefined
|
let rawDatasource: Datasource | undefined
|
||||||
|
@ -129,7 +132,8 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can persist views with all fields", async () => {
|
it("can persist views with all fields", async () => {
|
||||||
const newView: Required<Omit<CreateViewRequest, "query" | "type">> = {
|
const newView: Required<Omit<CreateViewRequest, "query" | "type">> =
|
||||||
|
{
|
||||||
name: generator.name(),
|
name: generator.name(),
|
||||||
tableId: table._id!,
|
tableId: table._id!,
|
||||||
primaryDisplay: "id",
|
primaryDisplay: "id",
|
||||||
|
@ -194,8 +198,9 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
|
|
||||||
it("can create a view with just a query field, no queryUI, for backwards compatibility", async () => {
|
it("can create a view with just a query field, no queryUI, for backwards compatibility", async () => {
|
||||||
const newView: Required<Omit<CreateViewRequest, "queryUI" | "type">> =
|
const newView: Required<
|
||||||
{
|
Omit<CreateViewRequest, "queryUI" | "type">
|
||||||
|
> = {
|
||||||
name: generator.name(),
|
name: generator.name(),
|
||||||
tableId: table._id!,
|
tableId: table._id!,
|
||||||
primaryDisplay: "id",
|
primaryDisplay: "id",
|
||||||
|
@ -1162,7 +1167,8 @@ datasourceDescribe(
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
expect(result.body).toEqual({
|
expect(result.body).toEqual({
|
||||||
message: "View id does not match between the body and the uri path",
|
message:
|
||||||
|
"View id does not match between the body and the uri path",
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -2016,7 +2022,10 @@ datasourceDescribe(
|
||||||
schema,
|
schema,
|
||||||
})
|
})
|
||||||
|
|
||||||
const renameColumn = async (table: Table, renaming: RenameColumn) => {
|
const renameColumn = async (
|
||||||
|
table: Table,
|
||||||
|
renaming: RenameColumn
|
||||||
|
) => {
|
||||||
const newSchema = { ...table.schema }
|
const newSchema = { ...table.schema }
|
||||||
newSchema[renaming.updated] = {
|
newSchema[renaming.updated] = {
|
||||||
...table.schema[renaming.old],
|
...table.schema[renaming.old],
|
||||||
|
@ -2617,7 +2626,9 @@ datasourceDescribe(
|
||||||
])
|
])
|
||||||
const rowUsage = await getRowUsage()
|
const rowUsage = await getRowUsage()
|
||||||
|
|
||||||
await config.api.row.bulkDelete(view.id, { rows: [rows[0], rows[2]] })
|
await config.api.row.bulkDelete(view.id, {
|
||||||
|
rows: [rows[0], rows[2]],
|
||||||
|
})
|
||||||
|
|
||||||
await assertRowUsage(isInternal ? rowUsage - 2 : rowUsage)
|
await assertRowUsage(isInternal ? rowUsage - 2 : rowUsage)
|
||||||
|
|
||||||
|
@ -3470,7 +3481,10 @@ datasourceDescribe(
|
||||||
expect(response.rows).toEqual(
|
expect(response.rows).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
"Quantity Sum": rows.reduce((acc, r) => acc + r.quantity, 0),
|
"Quantity Sum": rows.reduce(
|
||||||
|
(acc, r) => acc + r.quantity,
|
||||||
|
0
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
@ -3511,7 +3525,9 @@ datasourceDescribe(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const row of response.rows) {
|
for (const row of response.rows) {
|
||||||
expect(row["Total Price"]).toEqual(priceByQuantity[row.quantity])
|
expect(row["Total Price"]).toEqual(
|
||||||
|
priceByQuantity[row.quantity]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3701,9 +3717,12 @@ datasourceDescribe(
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const apertureScience = await config.api.row.save(companies._id!, {
|
const apertureScience = await config.api.row.save(
|
||||||
|
companies._id!,
|
||||||
|
{
|
||||||
name: "Aperture Science Laboratories",
|
name: "Aperture Science Laboratories",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const blackMesa = await config.api.row.save(companies._id!, {
|
const blackMesa = await config.api.row.save(companies._id!, {
|
||||||
name: "Black Mesa",
|
name: "Black Mesa",
|
||||||
|
@ -4402,7 +4421,9 @@ datasourceDescribe(
|
||||||
}),
|
}),
|
||||||
expected: () => [
|
expected: () => [
|
||||||
{
|
{
|
||||||
user: expect.objectContaining({ _id: config.getUser()._id }),
|
user: expect.objectContaining({
|
||||||
|
_id: config.getUser()._id,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -4632,3 +4653,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,13 @@ import {
|
||||||
import { Knex } from "knex"
|
import { Knex } from "knex"
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({
|
||||||
{
|
|
||||||
name: "execute query action",
|
|
||||||
exclude: [DatabaseName.MONGODB, DatabaseName.SQS],
|
exclude: [DatabaseName.MONGODB, DatabaseName.SQS],
|
||||||
},
|
})
|
||||||
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"execute query action ($dbName)",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let tableName: string
|
let tableName: string
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
@ -75,3 +77,4 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -433,9 +433,10 @@ describe("Automation Scenarios", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ only: [DatabaseName.MYSQL] })
|
||||||
{ name: "", only: [DatabaseName.MYSQL] },
|
|
||||||
({ config, dsProvider }) => {
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)("/rows ($dbName)", ({ config, dsProvider }) => {
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
|
||||||
|
@ -531,5 +532,5 @@ datasourceDescribe(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ function uniqueTableName(length?: number): string {
|
||||||
.substring(0, length || 10)
|
.substring(0, length || 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
datasourceDescribe(
|
const mainDescriptions = datasourceDescribe({ only: [DatabaseName.MYSQL] })
|
||||||
{
|
|
||||||
name: "Integration compatibility with mysql search_path",
|
if (mainDescriptions.length) {
|
||||||
only: [DatabaseName.MYSQL],
|
describe.each(mainDescriptions)(
|
||||||
},
|
"/Integration compatibility with mysql search_path ($dbName)",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let rawDatasource: Datasource
|
let rawDatasource: Datasource
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
|
@ -71,18 +71,20 @@ datasourceDescribe(
|
||||||
datasourceId: datasource._id!,
|
datasourceId: datasource._id!,
|
||||||
tablesFilter: [repeated_table_name],
|
tablesFilter: [repeated_table_name],
|
||||||
})
|
})
|
||||||
expect(res.datasource.entities![repeated_table_name].schema).toBeDefined()
|
expect(
|
||||||
|
res.datasource.entities![repeated_table_name].schema
|
||||||
|
).toBeDefined()
|
||||||
const schema = res.datasource.entities![repeated_table_name].schema
|
const schema = res.datasource.entities![repeated_table_name].schema
|
||||||
expect(Object.keys(schema).sort()).toEqual(["id", "val1"])
|
expect(Object.keys(schema).sort()).toEqual(["id", "val1"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ only: [DatabaseName.MYSQL] })
|
||||||
{
|
|
||||||
name: "POST /api/datasources/:datasourceId/schema",
|
if (descriptions.length) {
|
||||||
only: [DatabaseName.MYSQL],
|
describe.each(descriptions)(
|
||||||
},
|
"POST /api/datasources/:datasourceId/schema ($dbName)",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
@ -126,3 +128,5 @@ datasourceDescribe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,11 @@ import {
|
||||||
} from "../integrations/tests/utils"
|
} from "../integrations/tests/utils"
|
||||||
import { Knex } from "knex"
|
import { Knex } from "knex"
|
||||||
|
|
||||||
datasourceDescribe(
|
const mainDescriptions = datasourceDescribe({ only: [DatabaseName.POSTGRES] })
|
||||||
{ name: "postgres integrations", only: [DatabaseName.POSTGRES] },
|
|
||||||
|
if (mainDescriptions.length) {
|
||||||
|
describe.each(mainDescriptions)(
|
||||||
|
"/postgres integrations",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
@ -199,18 +202,21 @@ datasourceDescribe(
|
||||||
row = await config.api.row.save(table._id!, { ...row, price: 300 })
|
row = await config.api.row.save(table._id!, { ...row, price: 300 })
|
||||||
expect(row.price).toBe("300.00")
|
expect(row.price).toBe("300.00")
|
||||||
|
|
||||||
row = await config.api.row.save(table._id!, { ...row, price: "400.00" })
|
row = await config.api.row.save(table._id!, {
|
||||||
|
...row,
|
||||||
|
price: "400.00",
|
||||||
|
})
|
||||||
expect(row.price).toBe("400.00")
|
expect(row.price).toBe("400.00")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ only: [DatabaseName.POSTGRES] })
|
||||||
{
|
|
||||||
name: "Integration compatibility with postgres search_path",
|
if (descriptions.length) {
|
||||||
only: [DatabaseName.POSTGRES],
|
describe.each(descriptions)(
|
||||||
},
|
"Integration compatibility with postgres search_path",
|
||||||
({ config, dsProvider }) => {
|
({ config, dsProvider }) => {
|
||||||
let datasource: Datasource
|
let datasource: Datasource
|
||||||
let client: Knex
|
let client: Knex
|
||||||
|
@ -283,8 +289,11 @@ datasourceDescribe(
|
||||||
expect(
|
expect(
|
||||||
response.datasource.entities?.[repeated_table_name].schema
|
response.datasource.entities?.[repeated_table_name].schema
|
||||||
).toBeDefined()
|
).toBeDefined()
|
||||||
const schema = response.datasource.entities?.[repeated_table_name].schema
|
const schema =
|
||||||
|
response.datasource.entities?.[repeated_table_name].schema
|
||||||
expect(Object.keys(schema || {}).sort()).toEqual(["id", "val1"])
|
expect(Object.keys(schema || {}).sort()).toEqual(["id", "val1"])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -281,8 +281,14 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
|
||||||
case MSSQLConfigAuthType.NTLM: {
|
case MSSQLConfigAuthType.NTLM: {
|
||||||
const { domain, trustServerCertificate } =
|
const { domain, trustServerCertificate } =
|
||||||
this.config.ntlmConfig || {}
|
this.config.ntlmConfig || {}
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
throw Error("Domain must be provided for NTLM config")
|
||||||
|
}
|
||||||
|
|
||||||
clientCfg.authentication = {
|
clientCfg.authentication = {
|
||||||
type: "ntlm",
|
type: "ntlm",
|
||||||
|
// @ts-expect-error - username and password not required for NTLM
|
||||||
options: {
|
options: {
|
||||||
domain,
|
domain,
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
QueryType,
|
QueryType,
|
||||||
SqlQuery,
|
SqlQuery,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Snowflake } from "snowflake-promise"
|
import snowflakeSdk, { SnowflakeError } from "snowflake-sdk"
|
||||||
|
import { promisify } from "util"
|
||||||
|
|
||||||
interface SnowflakeConfig {
|
interface SnowflakeConfig {
|
||||||
account: string
|
account: string
|
||||||
|
@ -71,11 +72,52 @@ const SCHEMA: Integration = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class SnowflakeIntegration {
|
class SnowflakePromise {
|
||||||
private client: Snowflake
|
config: SnowflakeConfig
|
||||||
|
client?: snowflakeSdk.Connection
|
||||||
|
|
||||||
constructor(config: SnowflakeConfig) {
|
constructor(config: SnowflakeConfig) {
|
||||||
this.client = new Snowflake(config)
|
this.config = config
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
if (this.client?.isUp()) return
|
||||||
|
|
||||||
|
this.client = snowflakeSdk.createConnection(this.config)
|
||||||
|
const connectAsync = promisify(this.client.connect.bind(this.client))
|
||||||
|
return connectAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(sql: string) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.client) {
|
||||||
|
throw Error(
|
||||||
|
"No snowflake client present to execute query. Run connect() first to initialise."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.execute({
|
||||||
|
sqlText: sql,
|
||||||
|
complete: function (
|
||||||
|
err: SnowflakeError | undefined,
|
||||||
|
statementExecuted: any,
|
||||||
|
rows: any
|
||||||
|
) {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
resolve(rows)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnowflakeIntegration {
|
||||||
|
private client: SnowflakePromise
|
||||||
|
|
||||||
|
constructor(config: SnowflakeConfig) {
|
||||||
|
this.client = new SnowflakePromise(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
async testConnection(): Promise<ConnectionInfo> {
|
async testConnection(): Promise<ConnectionInfo> {
|
||||||
|
|
|
@ -35,7 +35,6 @@ const providers: Record<DatabaseName, DatasourceProvider> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatasourceDescribeOpts {
|
export interface DatasourceDescribeOpts {
|
||||||
name: string
|
|
||||||
only?: DatabaseName[]
|
only?: DatabaseName[]
|
||||||
exclude?: DatabaseName[]
|
exclude?: DatabaseName[]
|
||||||
}
|
}
|
||||||
|
@ -102,16 +101,12 @@ function createDummyTest() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function datasourceDescribe(
|
export function datasourceDescribe(opts: DatasourceDescribeOpts) {
|
||||||
opts: DatasourceDescribeOpts,
|
|
||||||
cb: (args: DatasourceDescribeReturn) => void
|
|
||||||
) {
|
|
||||||
if (process.env.DATASOURCE === "none") {
|
if (process.env.DATASOURCE === "none") {
|
||||||
createDummyTest()
|
createDummyTest()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, only, exclude } = opts
|
const { only, exclude } = opts
|
||||||
|
|
||||||
if (only && exclude) {
|
if (only && exclude) {
|
||||||
throw new Error("you can only supply one of 'only' or 'exclude'")
|
throw new Error("you can only supply one of 'only' or 'exclude'")
|
||||||
|
@ -130,36 +125,28 @@ export function datasourceDescribe(
|
||||||
|
|
||||||
if (databases.length === 0) {
|
if (databases.length === 0) {
|
||||||
createDummyTest()
|
createDummyTest()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe.each(databases)(name, name => {
|
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
|
return databases.map(dbName => ({
|
||||||
afterAll(() => {
|
dbName,
|
||||||
config.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
cb({
|
|
||||||
name,
|
|
||||||
config,
|
config,
|
||||||
dsProvider: () => createDatasources(config, name),
|
dsProvider: () => createDatasources(config, dbName),
|
||||||
isInternal: name === DatabaseName.SQS,
|
isInternal: dbName === DatabaseName.SQS,
|
||||||
isExternal: name !== DatabaseName.SQS,
|
isExternal: dbName !== DatabaseName.SQS,
|
||||||
isSql: [
|
isSql: [
|
||||||
DatabaseName.MARIADB,
|
DatabaseName.MARIADB,
|
||||||
DatabaseName.MYSQL,
|
DatabaseName.MYSQL,
|
||||||
DatabaseName.POSTGRES,
|
DatabaseName.POSTGRES,
|
||||||
DatabaseName.SQL_SERVER,
|
DatabaseName.SQL_SERVER,
|
||||||
DatabaseName.ORACLE,
|
DatabaseName.ORACLE,
|
||||||
].includes(name),
|
].includes(dbName),
|
||||||
isMySQL: name === DatabaseName.MYSQL,
|
isMySQL: dbName === DatabaseName.MYSQL,
|
||||||
isPostgres: name === DatabaseName.POSTGRES,
|
isPostgres: dbName === DatabaseName.POSTGRES,
|
||||||
isMongodb: name === DatabaseName.MONGODB,
|
isMongodb: dbName === DatabaseName.MONGODB,
|
||||||
isMSSQL: name === DatabaseName.SQL_SERVER,
|
isMSSQL: dbName === DatabaseName.SQL_SERVER,
|
||||||
isOracle: name === DatabaseName.ORACLE,
|
isOracle: dbName === DatabaseName.ORACLE,
|
||||||
})
|
}))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDatasource(
|
function getDatasource(
|
||||||
|
|
|
@ -19,8 +19,11 @@ import { tableForDatasource } from "../../../../../tests/utilities/structures"
|
||||||
// These test cases are only for things that cannot be tested through the API
|
// These test cases are only for things that cannot be tested through the API
|
||||||
// (e.g. limiting searches to returning specific fields). If it's possible to
|
// (e.g. limiting searches to returning specific fields). If it's possible to
|
||||||
// test through the API, it should be done there instead.
|
// test through the API, it should be done there instead.
|
||||||
datasourceDescribe(
|
const descriptions = datasourceDescribe({ exclude: [DatabaseName.MONGODB] })
|
||||||
{ name: "search sdk (%s)", exclude: [DatabaseName.MONGODB] },
|
|
||||||
|
if (descriptions.length) {
|
||||||
|
describe.each(descriptions)(
|
||||||
|
"search sdk ($dbName)",
|
||||||
({ config, dsProvider, isInternal }) => {
|
({ config, dsProvider, isInternal }) => {
|
||||||
let datasource: Datasource | undefined
|
let datasource: Datasource | undefined
|
||||||
let table: Table
|
let table: Table
|
||||||
|
@ -218,3 +221,4 @@ datasourceDescribe(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import jimp from "jimp"
|
import { Jimp } from "jimp"
|
||||||
|
|
||||||
const FORMATS = {
|
const FORMATS = {
|
||||||
IMAGES: ["png", "jpg", "jpeg", "gif", "bmp", "tiff"],
|
IMAGES: ["png", "jpg", "jpeg", "gif", "bmp", "tiff"],
|
||||||
|
@ -6,8 +6,8 @@ const FORMATS = {
|
||||||
|
|
||||||
function processImage(file: { path: string }) {
|
function processImage(file: { path: string }) {
|
||||||
// this will overwrite the temp file
|
// this will overwrite the temp file
|
||||||
return jimp.read(file.path).then(img => {
|
return Jimp.read(file.path).then(img => {
|
||||||
return img.resize(300, jimp.AUTO).write(file.path)
|
return img.resize({ w: 256 }).write(file.path as `${string}.${string}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,17 +40,17 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "0.0.0",
|
"@budibase/backend-core": "0.0.0",
|
||||||
"@budibase/pro": "0.0.0",
|
"@budibase/pro": "0.0.0",
|
||||||
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/string-templates": "0.0.0",
|
"@budibase/string-templates": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@koa/router": "13.1.0",
|
||||||
"@koa/router": "8.0.8",
|
|
||||||
"@techpass/passport-openidconnect": "0.3.3",
|
"@techpass/passport-openidconnect": "0.3.3",
|
||||||
"@types/global-agent": "2.1.1",
|
"@types/global-agent": "2.1.1",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1692.0",
|
||||||
"bcrypt": "5.1.0",
|
"bcrypt": "5.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bull": "4.10.1",
|
"bull": "4.10.1",
|
||||||
"dd-trace": "5.2.0",
|
"dd-trace": "5.23.0",
|
||||||
"dotenv": "8.6.0",
|
"dotenv": "8.6.0",
|
||||||
"global-agent": "3.0.0",
|
"global-agent": "3.0.0",
|
||||||
"ical-generator": "4.1.0",
|
"ical-generator": "4.1.0",
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
"@types/jsonwebtoken": "9.0.3",
|
"@types/jsonwebtoken": "9.0.3",
|
||||||
"@types/koa": "2.13.4",
|
"@types/koa": "2.13.4",
|
||||||
"@types/koa__router": "8.0.8",
|
"@types/koa__router": "12.0.4",
|
||||||
"@types/lodash": "4.14.200",
|
"@types/lodash": "4.14.200",
|
||||||
"@types/node": "^22.9.0",
|
"@types/node": "^22.9.0",
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
|
|
|
@ -40,6 +40,7 @@ import {
|
||||||
import { checkAnyUserExists } from "../../../utilities/users"
|
import { checkAnyUserExists } from "../../../utilities/users"
|
||||||
import { isEmailConfigured } from "../../../utilities/email"
|
import { isEmailConfigured } from "../../../utilities/email"
|
||||||
import { BpmStatusKey, BpmStatusValue, utils } from "@budibase/shared-core"
|
import { BpmStatusKey, BpmStatusValue, utils } from "@budibase/shared-core"
|
||||||
|
import crypto from "crypto"
|
||||||
|
|
||||||
const MAX_USERS_UPLOAD_LIMIT = 1000
|
const MAX_USERS_UPLOAD_LIMIT = 1000
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue