Merge branch 'develop' of github.com:Budibase/budibase into feature/test-image

This commit is contained in:
mike12345567 2022-12-13 15:44:34 +00:00
commit 998b7f4a81
72 changed files with 1543 additions and 3541 deletions

2
.gitignore vendored
View File

@ -106,3 +106,5 @@ stats.html
*.tsbuildinfo *.tsbuildinfo
budibase-component budibase-component
budibase-datasource budibase-datasource
*.iml

View File

@ -8,7 +8,7 @@
"editor.defaultFormatter": "vscode.json-language-features" "editor.defaultFormatter": "vscode.json-language-features"
}, },
"[javascript]": { "[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "vscode.typescript-language-features"
}, },
"debug.javascript.terminalOptions": { "debug.javascript.terminalOptions": {
"skipFiles": [ "skipFiles": [
@ -16,4 +16,7 @@
"<node_internals>/**" "<node_internals>/**"
] ]
}, },
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
@ -20,7 +20,7 @@
"test:watch": "jest --watchAll" "test:watch": "jest --watchAll"
}, },
"dependencies": { "dependencies": {
"@budibase/types": "2.1.46-alpha.3", "@budibase/types": "2.1.46-alpha.6",
"@shopify/jest-koa-mocks": "5.0.1", "@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0", "aws-sdk": "2.1030.0",

View File

@ -0,0 +1 @@
export * from "./src/plugin"

View File

@ -1,9 +1,13 @@
function isTest() { function isTest() {
return ( return isCypress() || isJest()
process.env.NODE_ENV === "jest" || }
process.env.NODE_ENV === "cypress" ||
process.env.JEST_WORKER_ID != null function isJest() {
) return !!(process.env.NODE_ENV === "jest" || process.env.JEST_WORKER_ID)
}
function isCypress() {
return process.env.NODE_ENV === "cypress"
} }
function isDev() { function isDev() {
@ -27,6 +31,7 @@ const DefaultBucketName = {
const environment = { const environment = {
isTest, isTest,
isJest,
isDev, isDev,
JS_BCRYPT: process.env.JS_BCRYPT, JS_BCRYPT: process.env.JS_BCRYPT,
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,

View File

@ -117,3 +117,7 @@ jest.spyOn(events.view, "filterDeleted")
jest.spyOn(events.view, "calculationCreated") jest.spyOn(events.view, "calculationCreated")
jest.spyOn(events.view, "calculationUpdated") jest.spyOn(events.view, "calculationUpdated")
jest.spyOn(events.view, "calculationDeleted") jest.spyOn(events.view, "calculationDeleted")
jest.spyOn(events.plugin, "init")
jest.spyOn(events.plugin, "imported")
jest.spyOn(events.plugin, "deleted")

View File

@ -2,4 +2,5 @@ import "./posthog"
import "./events" import "./events"
export * as accounts from "./accounts" export * as accounts from "./accounts"
export * as date from "./date" export * as date from "./date"
export * as licenses from "./licenses"
export { default as fetch } from "./fetch" export { default as fetch } from "./fetch"

View File

@ -0,0 +1,83 @@
import { Feature, License, Quotas } from "@budibase/types"
import _ from "lodash"
let CLOUD_FREE_LICENSE: License
let TEST_LICENSE: License
let getCachedLicense: any
// init for the packages other than pro
export function init(proPkg: any) {
initInternal({
CLOUD_FREE_LICENSE: proPkg.constants.licenses.CLOUD_FREE_LICENSE,
TEST_LICENSE: proPkg.constants.licenses.DEVELOPER_FREE_LICENSE,
getCachedLicense: proPkg.licensing.cache.getCachedLicense,
})
}
// init for the pro package
export function initInternal(opts: {
CLOUD_FREE_LICENSE: License
TEST_LICENSE: License
getCachedLicense: any
}) {
CLOUD_FREE_LICENSE = opts.CLOUD_FREE_LICENSE
TEST_LICENSE = opts.TEST_LICENSE
getCachedLicense = opts.getCachedLicense
}
export interface UseLicenseOpts {
features?: Feature[]
quotas?: Quotas
}
// LICENSES
export const useLicense = (license: License, opts?: UseLicenseOpts) => {
if (opts) {
if (opts.features) {
license.features.push(...opts.features)
}
if (opts.quotas) {
license.quotas = opts.quotas
}
}
getCachedLicense.mockReturnValue(license)
return license
}
export const useUnlimited = (opts?: UseLicenseOpts) => {
return useLicense(TEST_LICENSE, opts)
}
export const useCloudFree = () => {
return useLicense(CLOUD_FREE_LICENSE)
}
// FEATURES
const useFeature = (feature: Feature) => {
const license = _.cloneDeep(TEST_LICENSE)
const opts: UseLicenseOpts = {
features: [feature],
}
return useLicense(license, opts)
}
export const useBackups = () => {
return useFeature(Feature.APP_BACKUPS)
}
export const useGroups = () => {
return useFeature(Feature.USER_GROUPS)
}
// QUOTAS
export const setAutomationLogsQuota = (value: number) => {
const license = _.cloneDeep(TEST_LICENSE)
license.quotas.constant.automationLogRetentionDays.value = value
return useLicense(license)
}

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1", "@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1", "@spectrum-css/actiongroup": "1.0.1",
"@spectrum-css/avatar": "3.0.2", "@spectrum-css/avatar": "3.0.2",

View File

@ -2,7 +2,7 @@
# yarn lockfile v1 # yarn lockfile v1
"@adobe/spectrum-css-workflow-icons@^1.2.1": "@adobe/spectrum-css-workflow-icons@1.2.1":
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4" resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w== integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -71,10 +71,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.1.46-alpha.3", "@budibase/bbui": "2.1.46-alpha.6",
"@budibase/client": "2.1.46-alpha.3", "@budibase/client": "2.1.46-alpha.6",
"@budibase/frontend-core": "2.1.46-alpha.3", "@budibase/frontend-core": "2.1.46-alpha.6",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {
@ -26,9 +26,9 @@
"outputPath": "build" "outputPath": "build"
}, },
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.1.46-alpha.3", "@budibase/backend-core": "2.1.46-alpha.6",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@budibase/types": "2.1.46-alpha.3", "@budibase/types": "2.1.46-alpha.6",
"axios": "0.21.2", "axios": "0.21.2",
"chalk": "4.1.0", "chalk": "4.1.0",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "2.1.46-alpha.3", "@budibase/bbui": "2.1.46-alpha.6",
"@budibase/frontend-core": "2.1.46-alpha.3", "@budibase/frontend-core": "2.1.46-alpha.6",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "2.1.46-alpha.3", "@budibase/bbui": "2.1.46-alpha.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/sdk", "name": "@budibase/sdk",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase Public API SDK", "description": "Budibase Public API SDK",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",

View File

@ -1,3 +1,4 @@
import fs from "fs"
module FetchMock { module FetchMock {
const fetch = jest.requireActual("node-fetch") const fetch = jest.requireActual("node-fetch")
let failCount = 0 let failCount = 0
@ -92,6 +93,83 @@ module FetchMock {
value: value:
'<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en-GB"></html>', '<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en-GB"></html>',
}) })
} else if (
url === "https://api.github.com/repos/my-repo/budibase-comment-box"
) {
return Promise.resolve({
json: () => {
return {
name: "budibase-comment-box",
releases_url:
"https://api.github.com/repos/my-repo/budibase-comment-box{/id}",
}
},
})
} else if (
url === "https://api.github.com/repos/my-repo/budibase-comment-box/latest"
) {
return Promise.resolve({
json: () => {
return {
assets: [
{
content_type: "application/gzip",
browser_download_url:
"https://github.com/my-repo/budibase-comment-box/releases/download/v1.0.2/comment-box-1.0.2.tar.gz",
},
],
}
},
})
} else if (
url ===
"https://github.com/my-repo/budibase-comment-box/releases/download/v1.0.2/comment-box-1.0.2.tar.gz"
) {
return Promise.resolve({
body: fs.createReadStream(
"src/api/routes/tests/data/comment-box-1.0.2.tar.gz"
),
ok: true,
})
} else if (url === "https://www.npmjs.com/package/budibase-component") {
return Promise.resolve({
status: 200,
json: () => {
return {
name: "budibase-component",
"dist-tags": {
latest: "1.0.0",
},
versions: {
"1.0.0": {
dist: {
tarball:
"https://registry.npmjs.org/budibase-component/-/budibase-component-1.0.2.tgz",
},
},
},
}
},
})
} else if (
url ===
"https://registry.npmjs.org/budibase-component/-/budibase-component-1.0.2.tgz"
) {
return Promise.resolve({
body: fs.createReadStream(
"src/api/routes/tests/data/budibase-component-1.0.2.tgz"
),
ok: true,
})
} else if (
url === "https://www.someurl.com/comment-box/comment-box-1.0.2.tar.gz"
) {
return Promise.resolve({
body: fs.createReadStream(
"src/api/routes/tests/data/comment-box-1.0.2.tar.gz"
),
ok: true,
})
} else if (url.includes("failonce.com")) { } else if (url.includes("failonce.com")) {
failCount++ failCount++
if (failCount === 1) { if (failCount === 1) {

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -43,11 +43,11 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.0.3", "@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.1.46-alpha.3", "@budibase/backend-core": "2.1.46-alpha.6",
"@budibase/client": "2.1.46-alpha.3", "@budibase/client": "2.1.46-alpha.6",
"@budibase/pro": "2.1.46-alpha.3", "@budibase/pro": "2.1.46-alpha.6",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@budibase/types": "2.1.46-alpha.3", "@budibase/types": "2.1.46-alpha.6",
"@bull-board/api": "3.7.0", "@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4", "@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -12,13 +12,11 @@ jest.mock("../../../utilities/redis", () => ({
shutdown: jest.fn(), shutdown: jest.fn(),
})) }))
const { import { clearAllApps, checkBuilderEndpoint } from "./utilities/TestFunctions"
clearAllApps, import * as setup from "./utilities"
checkBuilderEndpoint, import { AppStatus } from "../../../db/utils"
} = require("./utilities/TestFunctions") import { events } from "@budibase/backend-core"
const setup = require("./utilities") import env from "../../../environment"
const { AppStatus } = require("../../../db/utils")
const { events } = require("@budibase/backend-core")
describe("/applications", () => { describe("/applications", () => {
let request = setup.getRequest() let request = setup.getRequest()
@ -234,4 +232,39 @@ describe("/applications", () => {
expect(getRes.body.application.updatedAt).toBeDefined() expect(getRes.body.application.updatedAt).toBeDefined()
}) })
}) })
describe("sync", () => {
it("app should sync correctly", async () => {
const res = await request
.post(`/api/applications/${config.getAppId()}/sync`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.message).toEqual("App sync completed successfully.")
})
it("app should not sync if production", async () => {
const res = await request
.post(`/api/applications/app_123456/sync`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(400)
expect(res.body.message).toEqual(
"This action cannot be performed for production apps"
)
})
it("app should not sync if sync is disabled", async () => {
env._set("DISABLE_AUTO_PROD_APP_SYNC", true)
const res = await request
.post(`/api/applications/${config.getAppId()}/sync`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body.message).toEqual(
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable."
)
env._set("DISABLE_AUTO_PROD_APP_SYNC", false)
})
})
}) })

View File

@ -8,10 +8,10 @@ jest.mock("@budibase/backend-core", () => {
} }
}) })
const { checkBuilderEndpoint } = require("./utilities/TestFunctions") import * as setup from "./utilities"
const setup = require("./utilities") import { events } from "@budibase/backend-core"
const { events } = require("@budibase/backend-core") import sdk from "../../../sdk"
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
describe("/backups", () => { describe("/backups", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()
@ -30,7 +30,7 @@ describe("/backups", () => {
.expect(200) .expect(200)
expect(res.text).toBeDefined() expect(res.text).toBeDefined()
expect(res.headers["content-type"]).toEqual("application/gzip") expect(res.headers["content-type"]).toEqual("application/gzip")
expect(events.app.exported.mock.calls.length).toBe(1) expect(events.app.exported).toBeCalledTimes(1)
}) })
it("should apply authorization to endpoint", async () => { it("should apply authorization to endpoint", async () => {
@ -41,4 +41,15 @@ describe("/backups", () => {
}) })
}) })
}) })
describe("calculateBackupStats", () => {
it("should be able to calculate the backup statistics", async () => {
config.createAutomation()
config.createScreen()
let res = await sdk.backups.calculateBackupStats(config.getAppId())
expect(res.automations).toEqual(1)
expect(res.datasources).toEqual(1)
expect(res.screens).toEqual(1)
})
})
}) })

View File

@ -0,0 +1,66 @@
import { db as dbCore } from "@budibase/backend-core"
import { AppStatus } from "../../../db/utils"
import * as setup from "./utilities"
describe("/cloud", () => {
let request = setup.getRequest()
let config = setup.getConfig()
afterAll(setup.afterAll)
beforeEach(async () => {
await config.init()
})
afterEach(async () => {
// clear all mocks
jest.clearAllMocks()
})
describe("import", () => {
it("should be able to import apps", async () => {
// first we need to delete any existing apps on the system so it looks clean otherwise the
// import will not run
await request
.delete(
`/api/applications/${dbCore.getProdAppID(
config.getAppId()
)}?unpublish=true`
)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
await request
.delete(`/api/applications/${config.getAppId()}`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
// get a count of apps before the import
const preImportApps = await request
.get(`/api/applications?status=${AppStatus.ALL}`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
// Perform the import
const res = await request
.post(`/api/cloud/import`)
.attach("importFile", "src/api/routes/tests/data/export-test.tar.gz")
.set(config.defaultHeaders())
.expect(200)
expect(res.body.message).toEqual("Apps successfully imported.")
// get a count of apps after the import
const postImportApps = await request
.get(`/api/applications?status=${AppStatus.ALL}`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
// There are two apps in the file that was imported so check for this
expect(postImportApps.body.length).toEqual(2)
})
})
})

View File

@ -1,16 +1,16 @@
jest.mock("pg") jest.mock("pg")
import * as setup from "./utilities"
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
import { checkCacheForDynamicVariable } from "../../../threads/utils"
import { events } from "@budibase/backend-core"
let setup = require("./utilities")
let { basicDatasource } = setup.structures let { basicDatasource } = setup.structures
let { checkBuilderEndpoint } = require("./utilities/TestFunctions")
const pg = require("pg") const pg = require("pg")
const { checkCacheForDynamicVariable } = require("../../../threads/utils")
const { events } = require("@budibase/backend-core")
describe("/datasources", () => { describe("/datasources", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()
let datasource let datasource: any
afterAll(setup.afterAll) afterAll(setup.afterAll)
@ -26,7 +26,7 @@ describe("/datasources", () => {
.post(`/api/datasources`) .post(`/api/datasources`)
.send(basicDatasource()) .send(basicDatasource())
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.datasource.name).toEqual("Test") expect(res.body.datasource.name).toEqual("Test")
@ -42,7 +42,7 @@ describe("/datasources", () => {
.put(`/api/datasources/${datasource._id}`) .put(`/api/datasources/${datasource._id}`)
.send(datasource) .send(datasource)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.datasource.name).toEqual("Updated Test") expect(res.body.datasource.name).toEqual("Updated Test")
@ -51,16 +51,25 @@ describe("/datasources", () => {
}) })
describe("dynamic variables", () => { describe("dynamic variables", () => {
async function preview(datasource, fields) { async function preview(
datasource: any,
fields: { path: string; queryString: string }
) {
return config.previewQuery(request, config, datasource, fields) return config.previewQuery(request, config, datasource, fields)
} }
it("should invalidate changed or removed variables", async () => { it("should invalidate changed or removed variables", async () => {
const { datasource, query } = await config.dynamicVariableDatasource() const { datasource, query } = await config.dynamicVariableDatasource()
// preview once to cache variables // preview once to cache variables
await preview(datasource, { path: "www.test.com", queryString: "test={{ variable3 }}" }) await preview(datasource, {
path: "www.test.com",
queryString: "test={{ variable3 }}",
})
// check variables in cache // check variables in cache
let contents = await checkCacheForDynamicVariable(query._id, "variable3") let contents = await checkCacheForDynamicVariable(
query._id,
"variable3"
)
expect(contents.rows.length).toEqual(1) expect(contents.rows.length).toEqual(1)
// update the datasource to remove the variables // update the datasource to remove the variables
@ -69,7 +78,7 @@ describe("/datasources", () => {
.put(`/api/datasources/${datasource._id}`) .put(`/api/datasources/${datasource._id}`)
.send(datasource) .send(datasource)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.errors).toBeUndefined() expect(res.body.errors).toBeUndefined()
@ -85,7 +94,7 @@ describe("/datasources", () => {
const res = await request const res = await request
.get(`/api/datasources`) .get(`/api/datasources`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
const datasources = res.body const datasources = res.body
@ -160,7 +169,7 @@ describe("/datasources", () => {
const res = await request const res = await request
.get(`/api/datasources`) .get(`/api/datasources`)
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect('Content-Type', /json/) .expect("Content-Type", /json/)
.expect(200) .expect(200)
expect(res.body.length).toEqual(1) expect(res.body.length).toEqual(1)
@ -174,6 +183,5 @@ describe("/datasources", () => {
url: `/api/datasources/${datasource._id}/${datasource._rev}`, url: `/api/datasources/${datasource._id}/${datasource._rev}`,
}) })
}) })
}) })
}) })

View File

@ -0,0 +1,179 @@
let mockObjectStore = jest.fn().mockImplementation(() => {
return [{ name: "test.js" }]
})
let deleteFolder = jest.fn().mockImplementation()
jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
objectStore: {
...core.objectStore,
upload: jest.fn(),
uploadDirectory: mockObjectStore,
deleteFolder: deleteFolder,
},
}
})
import { events } from "@budibase/backend-core"
import * as setup from "./utilities"
describe("/plugins", () => {
let request = setup.getRequest()
let config = setup.getConfig()
afterAll(setup.afterAll)
beforeEach(async () => {
await config.init()
jest.clearAllMocks()
})
const createPlugin = async (status?: number) => {
return request
.post(`/api/plugin/upload`)
.attach("file", "src/api/routes/tests/data/comment-box-1.0.2.tar.gz")
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(status ? status : 200)
}
const getPlugins = async (status?: number) => {
return request
.get(`/api/plugin`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(status ? status : 200)
}
describe("upload", () => {
it("should be able to upload a plugin", async () => {
let res = await createPlugin()
expect(res.body).toBeDefined()
expect(res.body.plugins).toBeDefined()
expect(res.body.plugins[0]._id).toEqual("plg_comment-box")
expect(events.plugin.imported).toHaveBeenCalledTimes(1)
})
it("should not be able to create a plugin if there is an error", async () => {
mockObjectStore.mockImplementationOnce(() => {
throw new Error()
})
let res = await createPlugin(400)
expect(res.body.message).toEqual("Failed to import plugin: Error")
expect(events.plugin.imported).toHaveBeenCalledTimes(0)
})
})
describe("fetch", () => {
it("should be able to fetch plugins", async () => {
await createPlugin()
const res = await getPlugins()
expect(res.body).toBeDefined()
expect(res.body[0]._id).toEqual("plg_comment-box")
})
})
describe("destroy", () => {
it("should be able to delete a plugin", async () => {
await createPlugin()
const res = await request
.delete(`/api/plugin/plg_comment-box`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body).toBeDefined()
expect(res.body.message).toEqual("Plugin plg_comment-box deleted.")
const plugins = await getPlugins()
expect(plugins.body).toBeDefined()
expect(plugins.body.length).toEqual(0)
expect(events.plugin.deleted).toHaveBeenCalledTimes(1)
})
it("should handle an error deleting a plugin", async () => {
deleteFolder.mockImplementationOnce(() => {
throw new Error()
})
await createPlugin()
const res = await request
.delete(`/api/plugin/plg_comment-box`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(400)
expect(res.body.message).toEqual("Failed to delete plugin: Error")
expect(events.plugin.deleted).toHaveBeenCalledTimes(0)
const plugins = await getPlugins()
expect(plugins.body).toBeDefined()
expect(plugins.body.length).toEqual(1)
})
})
describe("github", () => {
const createGithubPlugin = async (status?: number, url?: string) => {
return await request
.post(`/api/plugin`)
.send({
source: "Github",
url,
githubToken: "token",
})
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(status ? status : 200)
}
it("should be able to create a plugin from github", async () => {
const res = await createGithubPlugin(
200,
"https://github.com/my-repo/budibase-comment-box.git"
)
expect(res.body).toBeDefined()
expect(res.body.plugin).toBeDefined()
expect(res.body.plugin._id).toEqual("plg_comment-box")
})
it("should fail if the url is not from github", async () => {
const res = await createGithubPlugin(
400,
"https://notgithub.com/my-repo/budibase-comment-box"
)
expect(res.body.message).toEqual(
"Failed to import plugin: The plugin origin must be from Github"
)
})
})
describe("npm", () => {
it("should be able to create a plugin from npm", async () => {
const res = await request
.post(`/api/plugin`)
.send({
source: "NPM",
url: "https://www.npmjs.com/package/budibase-component",
})
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body).toBeDefined()
expect(res.body.plugin._id).toEqual("plg_budibase-component")
expect(events.plugin.imported).toHaveBeenCalled()
})
})
describe("url", () => {
it("should be able to create a plugin from a URL", async () => {
const res = await request
.post(`/api/plugin`)
.send({
source: "URL",
url: "https://www.someurl.com/comment-box/comment-box-1.0.2.tar.gz",
})
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
expect(res.body).toBeDefined()
expect(res.body.plugin._id).toEqual("plg_comment-box")
expect(events.plugin.imported).toHaveBeenCalledTimes(1)
})
})
})

View File

@ -9,7 +9,6 @@ const route = "/test"
// there are checks which are disabled in test env, // there are checks which are disabled in test env,
// these checks need to be enabled for this test // these checks need to be enabled for this test
describe("/routing", () => { describe("/routing", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()

View File

@ -87,6 +87,12 @@ describe("/tables", () => {
it("updates all the row fields for a table when a schema key is renamed", async () => { it("updates all the row fields for a table when a schema key is renamed", async () => {
const testTable = await config.createTable() const testTable = await config.createTable()
await config.createView({
name: "TestView",
field: "Price",
calculation: "stats",
tableId: testTable._id,
})
const testRow = await request const testRow = await request
.post(`/api/${testTable._id}/rows`) .post(`/api/${testTable._id}/rows`)
@ -109,7 +115,7 @@ describe("/tables", () => {
updated: "updatedName" updated: "updatedName"
}, },
schema: { schema: {
updatedName: {type: "string"} updatedName: { type: "string" }
} }
}) })
.set(config.defaultHeaders()) .set(config.defaultHeaders())

View File

@ -90,4 +90,90 @@ describe("/users", () => {
expect(res.body.tableId).toBeDefined() expect(res.body.tableId).toBeDefined()
}) })
}) })
describe("setFlag", () => {
it("should throw an error if a flag is not provided", async () => {
await config.createUser()
const res = await request
.post(`/api/users/flags`)
.set(config.defaultHeaders())
.send({ value: "test" })
.expect(400)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual("Must supply a 'flag' field in request body.")
})
it("should be able to set a flag on the user", async () => {
await config.createUser()
const res = await request
.post(`/api/users/flags`)
.set(config.defaultHeaders())
.send({ value: "test", flag: "test" })
.expect(200)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual("Flag set successfully")
})
})
describe("getFlags", () => {
it("should get flags for a specific user", async () => {
let flagData = { value: "test", flag: "test" }
await config.createUser()
await request
.post(`/api/users/flags`)
.set(config.defaultHeaders())
.send(flagData)
.expect(200)
.expect("Content-Type", /json/)
const res = await request
.get(`/api/users/flags`)
.set(config.defaultHeaders())
.expect(200)
.expect("Content-Type", /json/)
expect(res.body[flagData.value]).toEqual(flagData.flag)
})
})
describe("setFlag", () => {
it("should throw an error if a flag is not provided", async () => {
await config.createUser()
const res = await request
.post(`/api/users/flags`)
.set(config.defaultHeaders())
.send({ value: "test" })
.expect(400)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual("Must supply a 'flag' field in request body.")
})
it("should be able to set a flag on the user", async () => {
await config.createUser()
const res = await request
.post(`/api/users/flags`)
.set(config.defaultHeaders())
.send({ value: "test", flag: "test" })
.expect(200)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual("Flag set successfully")
})
})
describe("syncUser", () => {
it("should sync the user", async () => {
let user = await config.createUser()
await config.createApp('New App')
let res = await request
.post(`/api/users/metadata/sync/${user._id}`)
.set(config.defaultHeaders())
.expect(200)
.expect("Content-Type", /json/)
expect(res.body.message).toEqual('User synced.')
})
})
}) })

View File

@ -63,14 +63,14 @@ export function afterAll() {
export function getRequest() { export function getRequest() {
if (!request) { if (!request) {
exports.beforeAll() beforeAll()
} }
return request return request
} }
export function getConfig() { export function getConfig() {
if (!config) { if (!config) {
exports.beforeAll() beforeAll()
} }
return config return config
} }

View File

@ -5,6 +5,7 @@ import {
} from "@budibase/string-templates" } from "@budibase/string-templates"
import sdk from "../sdk" import sdk from "../sdk"
import { Row } from "@budibase/types" import { Row } from "@budibase/types"
import { LoopStep, LoopStepType, LoopInput } from "../definitions/automations"
/** /**
* When values are input to the system generally they will be of type string as this is required for template strings. * When values are input to the system generally they will be of type string as this is required for template strings.
@ -123,3 +124,26 @@ export function stringSplit(value: string | string[]) {
} }
return value return value
} }
export function typecastForLooping(loopStep: LoopStep, input: LoopInput) {
if (!input || !input.binding) {
return null
}
try {
switch (loopStep.inputs.option) {
case LoopStepType.ARRAY:
if (typeof input.binding === "string") {
return JSON.parse(input.binding)
}
break
case LoopStepType.STRING:
if (Array.isArray(input.binding)) {
return input.binding.join(",")
}
break
}
} catch (err) {
throw new Error("Unable to cast to correct type")
}
return input.binding
}

View File

@ -0,0 +1,34 @@
const setup = require("./utilities")
describe("test the bash action", () => {
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
})
afterAll(setup.afterAll)
it("should be able to execute a script", async () => {
let res = await setup.runStep("EXECUTE_BASH",
inputs = {
code: "echo 'test'"
}
)
expect(res.stdout).toEqual("test\n")
expect(res.success).toEqual(true)
})
it("should handle a null value", async () => {
let res = await setup.runStep("EXECUTE_BASH",
inputs = {
code: null
}
)
expect(res.stdout).toEqual("Budibase bash automation failed: Invalid inputs")
})
})

View File

@ -0,0 +1,27 @@
const setup = require("./utilities")
const fetch = require("node-fetch")
jest.mock("node-fetch")
describe("test the outgoing webhook action", () => {
let inputs
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
inputs = {
username: "joe_bloggs",
url: "http://www.test.com",
}
})
afterAll(setup.afterAll)
it("should be able to run the action", async () => {
const res = await setup.runStep(setup.actions.discord.stepId, inputs)
expect(res.response.url).toEqual("http://www.test.com")
expect(res.response.method).toEqual("post")
expect(res.success).toEqual(true)
})
})

View File

@ -0,0 +1,49 @@
const setup = require("./utilities")
describe("test the execute query action", () => {
let datasource
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
await config.createDatasource()
query = await config.createQuery()
})
afterAll(setup.afterAll)
it("should be able to execute a query", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId,
inputs = {
query: { queryId: query._id }
}
)
expect(res.response).toEqual([{ a: 'string', b: 1 }])
expect(res.success).toEqual(true)
})
it("should handle a null query value", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId,
inputs = {
query: null
}
)
expect(res.response.message).toEqual("Invalid inputs")
expect(res.success).toEqual(false)
})
it("should handle an error executing a query", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_QUERY.stepId,
inputs = {
query: { queryId: "wrong_id" }
}
)
expect(res.response).toEqual('{"status":404,"name":"not_found","message":"missing","reason":"missing"}')
expect(res.success).toEqual(false)
})
})

View File

@ -0,0 +1,48 @@
const setup = require("./utilities")
describe("test the execute script action", () => {
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
})
afterAll(setup.afterAll)
it("should be able to execute a script", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId,
inputs = {
code: "return 1 + 1"
}
)
expect(res.value).toEqual(2)
expect(res.success).toEqual(true)
})
it("should handle a null value", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId,
inputs = {
code: null
}
)
expect(res.response.message).toEqual("Invalid inputs")
expect(res.success).toEqual(false)
})
it("should be able to handle an error gracefully", async () => {
let res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId,
inputs = {
code: "return something.map(x => x.name)"
}
)
expect(res.response).toEqual("ReferenceError: something is not defined")
expect(res.success).toEqual(false)
})
})

View File

@ -0,0 +1,71 @@
function generateResponse(to, from) {
return {
"success": true,
"response": {
"accepted": [
to
],
"envelope": {
"from": from,
"to": [
to
]
},
"message": `Email sent to ${to}.`
}
}
}
const mockFetch = jest.fn(() => ({
headers: {
raw: () => {
return { "content-type": ["application/json"] }
},
get: () => ["application/json"],
},
json: jest.fn(() => response),
status: 200,
text: jest.fn(),
}))
jest.mock("node-fetch", () => mockFetch)
const setup = require("./utilities")
describe("test the outgoing webhook action", () => {
let inputs
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
})
afterAll(setup.afterAll)
it("should be able to run the action", async () => {
inputs = {
to: "user1@test.com",
from: "admin@test.com",
subject: "hello",
contents: "testing",
}
let resp = generateResponse(inputs.to, inputs.from)
mockFetch.mockImplementationOnce(() => ({
headers: {
raw: () => {
return { "content-type": ["application/json"] }
},
get: () => ["application/json"],
},
json: jest.fn(() => resp),
status: 200,
text: jest.fn(),
}))
const res = await setup.runStep(setup.actions.SEND_EMAIL_SMTP.stepId, inputs)
expect(res.response).toEqual(resp)
expect(res.success).toEqual(true)
})
})

View File

@ -0,0 +1,22 @@
const setup = require("./utilities")
describe("test the server log action", () => {
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
inputs = {
text: "log message",
}
})
afterAll(setup.afterAll)
it("should be able to log the text", async () => {
let res = await setup.runStep(setup.actions.SERVER_LOG.stepId,
inputs
)
expect(res.message).toEqual(`App ${config.getAppId()} - ${inputs.text}`)
expect(res.success).toEqual(true)
})
})

View File

@ -31,7 +31,7 @@ export async function runInProd(fn: any) {
} }
} }
export async function runStep(stepId: string, inputs: any) { export async function runStep(stepId: string, inputs: any, stepContext?: any) {
async function run() { async function run() {
let step = await getAction(stepId) let step = await getAction(stepId)
expect(step).toBeDefined() expect(step).toBeDefined()
@ -39,7 +39,7 @@ export async function runStep(stepId: string, inputs: any) {
throw new Error("No step found") throw new Error("No step found")
} }
return step({ return step({
context: {}, context: stepContext || {},
inputs, inputs,
appId: config ? config.getAppId() : null, appId: config ? config.getAppId() : null,
// don't really need an API key, mocked out usage quota, not being tested here // don't really need an API key, mocked out usage quota, not being tested here

View File

@ -0,0 +1,27 @@
const setup = require("./utilities")
const fetch = require("node-fetch")
jest.mock("node-fetch")
describe("test the outgoing webhook action", () => {
let inputs
let config = setup.getConfig()
beforeEach(async () => {
await config.init()
inputs = {
value1: "test",
url: "http://www.test.com",
}
})
afterAll(setup.afterAll)
it("should be able to run the action", async () => {
const res = await setup.runStep(setup.actions.zapier.stepId, inputs)
expect(res.response.url).toEqual("http://www.test.com")
expect(res.response.method).toEqual("post")
expect(res.success).toEqual(true)
})
})

View File

@ -1,17 +0,0 @@
const automationUtils = require("../automationUtils")
describe("automationUtils", () => {
test("substituteLoopStep should allow multiple loop binding substitutes", () => {
expect(automationUtils.substituteLoopStep(
`{{ loop.currentItem._id }} {{ loop.currentItem._id }} {{ loop.currentItem._id }}`,
"step.2"))
.toBe(`{{ step.2.currentItem._id }} {{ step.2.currentItem._id }} {{ step.2.currentItem._id }}`)
})
test("substituteLoopStep should handle not subsituting outside of curly braces", () => {
expect(automationUtils.substituteLoopStep(
`loop {{ loop.currentItem._id }}loop loop{{ loop.currentItem._id }}loop`,
"step.2"))
.toBe(`loop {{ step.2.currentItem._id }}loop loop{{ step.2.currentItem._id }}loop`)
})
})

View File

@ -0,0 +1,65 @@
const automationUtils = require("../automationUtils")
describe("automationUtils", () => {
describe("substituteLoopStep", () => {
it("should allow multiple loop binding substitutes", () => {
expect(
automationUtils.substituteLoopStep(
`{{ loop.currentItem._id }} {{ loop.currentItem._id }} {{ loop.currentItem._id }}`,
"step.2"
)
).toBe(
`{{ step.2.currentItem._id }} {{ step.2.currentItem._id }} {{ step.2.currentItem._id }}`
)
})
it("should handle not subsituting outside of curly braces", () => {
expect(
automationUtils.substituteLoopStep(
`loop {{ loop.currentItem._id }}loop loop{{ loop.currentItem._id }}loop`,
"step.2"
)
).toBe(
`loop {{ step.2.currentItem._id }}loop loop{{ step.2.currentItem._id }}loop`
)
})
})
describe("typeCastForLooping", () => {
it("should parse to correct type", () => {
expect(
automationUtils.typecastForLooping(
{ inputs: { option: "Array" } },
{ binding: [1, 2, 3] }
)
).toEqual([1, 2, 3])
expect(
automationUtils.typecastForLooping(
{ inputs: { option: "Array" } },
{ binding: "[1, 2, 3]" }
)
).toEqual([1, 2, 3])
expect(
automationUtils.typecastForLooping(
{ inputs: { option: "String" } },
{ binding: [1, 2, 3] }
)
).toEqual("1,2,3")
})
it("should handle null values", () => {
// expect it to handle where the binding is null
expect(
automationUtils.typecastForLooping(
{ inputs: { option: "Array" } },
{ binding: null }
)
).toEqual(null)
expect(() =>
automationUtils.typecastForLooping(
{ inputs: { option: "Array" } },
{ binding: "test" }
)
).toThrow()
})
})
})

View File

@ -1,3 +1,12 @@
import { mocks } from "@budibase/backend-core/tests"
// init the licensing mock
import * as pro from "@budibase/pro"
mocks.licenses.init(pro)
// use unlimited license by default
mocks.licenses.useUnlimited()
import { init as dbInit } from "../../db" import { init as dbInit } from "../../db"
dbInit() dbInit()
import env from "../../environment" import env from "../../environment"

View File

@ -32,31 +32,8 @@ const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
const CRON_STEP_ID = triggerDefs.CRON.stepId const CRON_STEP_ID = triggerDefs.CRON.stepId
const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED } const STOPPED_STATUS = { success: true, status: AutomationStatus.STOPPED }
function typecastForLooping(loopStep: LoopStep, input: LoopInput) {
if (!input || !input.binding) {
return null
}
try {
switch (loopStep.inputs.option) {
case LoopStepType.ARRAY:
if (typeof input.binding === "string") {
return JSON.parse(input.binding)
}
break
case LoopStepType.STRING:
if (Array.isArray(input.binding)) {
return input.binding.join(",")
}
break
}
} catch (err) {
throw new Error("Unable to cast to correct type")
}
return input.binding
}
function getLoopIterations(loopStep: LoopStep, input: LoopInput) { function getLoopIterations(loopStep: LoopStep, input: LoopInput) {
const binding = typecastForLooping(loopStep, input) const binding = automationUtils.typecastForLooping(loopStep, input)
if (!loopStep || !binding) { if (!loopStep || !binding) {
return 1 return 1
} }
@ -289,7 +266,7 @@ class Orchestrator {
let tempOutput = { items: loopSteps, iterations: iterationCount } let tempOutput = { items: loopSteps, iterations: iterationCount }
try { try {
newInput.binding = typecastForLooping( newInput.binding = automationUtils.typecastForLooping(
loopStep as LoopStep, loopStep as LoopStep,
newInput newInput
) )

View File

@ -0,0 +1,57 @@
import { fixAutoColumnSubType } from "../utils"
import { AutoFieldDefaultNames, AutoFieldSubTypes } from "../../../constants"
describe("rowProcessor utility", () => {
describe("fixAutoColumnSubType", () => {
let schema = {
name: "",
type: "link",
subtype: "", // missing subtype
icon: "ri-magic-line",
autocolumn: true,
constraints: { type: "array", presence: false },
tableId: "ta_users",
fieldName: "test-Updated By",
relationshipType: "many-to-many",
sortable: false,
}
it("updates the schema with the correct subtype", async () => {
schema.name = AutoFieldDefaultNames.CREATED_BY
expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.CREATED_BY
)
schema.subtype = ""
schema.name = AutoFieldDefaultNames.UPDATED_BY
expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.UPDATED_BY
)
schema.subtype = ""
schema.name = AutoFieldDefaultNames.CREATED_AT
expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.CREATED_AT
)
schema.subtype = ""
schema.name = AutoFieldDefaultNames.UPDATED_AT
expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.UPDATED_AT
)
schema.subtype = ""
schema.name = AutoFieldDefaultNames.AUTO_ID
expect(fixAutoColumnSubType(schema).subtype).toEqual(
AutoFieldSubTypes.AUTO_ID
)
schema.subtype = ""
})
it("returns the column if subtype exists", async () => {
schema.subtype = AutoFieldSubTypes.CREATED_BY
schema.name = AutoFieldDefaultNames.CREATED_AT
expect(fixAutoColumnSubType(schema)).toEqual(schema)
})
})
})

View File

@ -0,0 +1,23 @@
import { enrichPluginURLs } from "../plugins"
const env = require("../../environment")
jest.mock("../../environment")
describe("plugins utility", () => {
let pluginsArray: any = [
{
name: "test-plugin",
},
]
it("enriches the plugins url self-hosted", async () => {
let result = enrichPluginURLs(pluginsArray)
expect(result[0].jsUrl).toEqual("/plugins/test-plugin/plugin.min.js")
})
it("enriches the plugins url cloud", async () => {
env.SELF_HOSTED = 0
let result = enrichPluginURLs(pluginsArray)
expect(result[0].jsUrl).toEqual(
"https://cdn.budi.live/test-plugin/plugin.min.js"
)
})
})

View File

@ -19,6 +19,8 @@
"node_modules", "node_modules",
"dist", "dist",
"src/tests", "src/tests",
"src/api/routes/tests/utilities",
"src/automations/tests/utilities",
"**/*.spec.ts", "**/*.spec.ts",
"**/*.spec.js" "**/*.spec.js"
] ]

View File

@ -1273,12 +1273,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.1.46-alpha.3": "@budibase/backend-core@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.3.tgz#f8caf2af9a8d3a16d4c4280f365567581f9b55a2" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.6.tgz#eb24abae6e3f6435a01b97978d25a466b672caff"
integrity sha512-osyuJq9db0DeUkaj4uANzo1mMt7SuKO5vSBITemLua0K8T8Z4r2ypE4muktEsfBdPxAH4cclMg/JaYl4RM8bwQ== integrity sha512-oDPhUE1nPoBu74lWQFj+9p8Fxh42CbNiE+PqaIBrcjpgSmg88Ftcr82UHg3YPQSXGBa/7hVvIkyXqVYzhIfG/Q==
dependencies: dependencies:
"@budibase/types" "2.1.46-alpha.3" "@budibase/types" "2.1.46-alpha.6"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
@ -1360,13 +1360,13 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
"@budibase/pro@2.1.46-alpha.3": "@budibase/pro@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.3.tgz#88e13775402561f1bd8d20483493a34082a6d8ab" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.6.tgz#c81465fe03c1a2dac69308ce5304e423bfbcabf4"
integrity sha512-B3z/Jk4g1ig8Wx62KmjAeYeITePxwrLHnSoy/Ugz6APNfNiXe7Y/ilQ5BFHWB0z/z3/8Vs1sOdP5c3/R5LpqDQ== integrity sha512-76/29biUDsGfOE4nzMHuVyzTpXPXsNOSe1dkbhGvxBVn42CQGIaR17a+0do9XX5I9qn7zhFJmz2B3UYYb9rZ4g==
dependencies: dependencies:
"@budibase/backend-core" "2.1.46-alpha.3" "@budibase/backend-core" "2.1.46-alpha.6"
"@budibase/types" "2.1.46-alpha.3" "@budibase/types" "2.1.46-alpha.6"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
@ -1390,10 +1390,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/types@2.1.46-alpha.3": "@budibase/types@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.3.tgz#ffd96e1f3b006af5f0c0900e927d0454a2e61c53" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.6.tgz#d80f47aa57ffa0685f03f5aaf5477d1e985fc9cf"
integrity sha512-JIO5qH/UYbIays/3dDovltiUEL3a4npXZIMlGgARzPQ5DW7ZB8hfJ5fXPt+BsbMXeaJAEsRbDkx82MDQs4y5Lg== integrity sha512-ol0/j0h5A6ZCQrc+qGkigFcuQ8EsyTLhHEhBynh/TWyTbjbUWPJBGTeY5lYzWD2bqQWnRDXsDP4iNdpbuviZNA==
"@bull-board/api@3.7.0": "@bull-board/api@3.7.0":
version "3.7.0" version "3.7.0"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/types", "name": "@budibase/types",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase types", "description": "Budibase types",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",

View File

@ -19,6 +19,8 @@ if (!process.env.CI) {
} }
// add pro sources if they exist // add pro sources if they exist
if (fs.existsSync("../../../budibase-pro")) { if (fs.existsSync("../../../budibase-pro")) {
config.moduleNameMapper["@budibase/pro/(.*)"] =
"<rootDir>/../../../budibase-pro/packages/pro/$1"
config.moduleNameMapper["@budibase/pro"] = config.moduleNameMapper["@budibase/pro"] =
"<rootDir>/../../../budibase-pro/packages/pro/src" "<rootDir>/../../../budibase-pro/packages/pro/src"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "2.1.46-alpha.3", "version": "2.1.46-alpha.6",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -36,10 +36,10 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "2.1.46-alpha.3", "@budibase/backend-core": "2.1.46-alpha.6",
"@budibase/pro": "2.1.46-alpha.3", "@budibase/pro": "2.1.46-alpha.6",
"@budibase/string-templates": "2.1.46-alpha.3", "@budibase/string-templates": "2.1.46-alpha.6",
"@budibase/types": "2.1.46-alpha.3", "@budibase/types": "2.1.46-alpha.6",
"@koa/router": "8.0.8", "@koa/router": "8.0.8",
"@sentry/node": "6.17.7", "@sentry/node": "6.17.7",
"@techpass/passport-openidconnect": "0.3.2", "@techpass/passport-openidconnect": "0.3.2",

View File

@ -0,0 +1,58 @@
import { events } from "@budibase/backend-core"
import { structures, TestConfiguration, mocks } from "../../../../tests"
describe("/api/global/groups", () => {
const config = new TestConfiguration()
beforeAll(async () => {
await config.beforeAll()
})
afterAll(async () => {
await config.afterAll()
})
beforeEach(async () => {
mocks.licenses.useGroups()
})
describe("create", () => {
it("should be able to create a new group", async () => {
const group = structures.groups.UserGroup()
await config.api.groups.saveGroup(group)
expect(events.group.created).toBeCalledTimes(1)
expect(events.group.updated).not.toBeCalled()
expect(events.group.permissionsEdited).not.toBeCalled()
})
})
describe("update", () => {
it("should be able to update a basic group", async () => {
const group = structures.groups.UserGroup()
let oldGroup = await config.api.groups.saveGroup(group)
let updatedGroup = {
...oldGroup.body,
...group,
name: "New Name",
}
await config.api.groups.saveGroup(updatedGroup)
expect(events.group.updated).toBeCalledTimes(1)
expect(events.group.permissionsEdited).not.toBeCalled()
})
describe("destroy", () => {
it("should be able to delete a basic group", async () => {
const group = structures.groups.UserGroup()
let oldGroup = await config.api.groups.saveGroup(group)
await config.api.groups.deleteGroup(
oldGroup.body._id,
oldGroup.body._rev
)
expect(events.group.deleted).toBeCalledTimes(1)
})
})
})
})

View File

@ -1,7 +1,5 @@
import { TestConfiguration } from "../../../../tests" import { TestConfiguration } from "../../../../tests"
// TODO
describe("/api/global/license", () => { describe("/api/global/license", () => {
const config = new TestConfiguration() const config = new TestConfiguration()

View File

@ -1,11 +1,47 @@
import { TestConfiguration } from "../../../../tests" import { structures, TestConfiguration } from "../../../../tests"
import { context, db, permissions, roles } from "@budibase/backend-core"
import { Mock } from "jest-mock"
// TODO jest.mock("@budibase/backend-core", () => {
const core = jest.requireActual("@budibase/backend-core")
return {
...core,
db: {
...core.db,
},
context: {
...core.context,
getAppDB: jest.fn(),
},
}
})
const appDb = db.getDB("app_test")
const mockAppDB = context.getAppDB as Mock
mockAppDB.mockReturnValue(appDb)
async function addAppMetadata() {
await appDb.put({
_id: "app_metadata",
appId: "app_test",
name: "New App",
version: "version",
url: "url",
})
}
describe("/api/global/roles", () => { describe("/api/global/roles", () => {
const config = new TestConfiguration() const config = new TestConfiguration()
const role = new roles.Role(
db.generateRoleID("newRole"),
roles.BUILTIN_ROLE_IDS.BASIC,
permissions.BuiltinPermissionID.READ_ONLY
)
beforeAll(async () => { beforeAll(async () => {
console.debug(role)
appDb.put(role)
await addAppMetadata()
await config.beforeAll() await config.beforeAll()
}) })
@ -18,10 +54,35 @@ describe("/api/global/roles", () => {
}) })
describe("GET /api/global/roles", () => { describe("GET /api/global/roles", () => {
it("retrieves roles", () => {}) it("retrieves roles", async () => {
const res = await config.api.roles.get()
expect(res.body).toBeDefined()
expect(res.body["app_test"].roles.length).toEqual(5)
expect(res.body["app_test"].roles.map((r: any) => r._id)).toContain(
role._id
)
})
}) })
describe("GET /api/global/roles/:appId", () => {}) describe("GET api/global/roles/:appId", () => {
it("finds a role by appId", async () => {
const res = await config.api.roles.find("app_test")
expect(res.body).toBeDefined()
expect(res.body.name).toEqual("New App")
})
})
describe("DELETE /api/global/roles/:appId", () => {}) describe("DELETE /api/global/roles/:appId", () => {
it("removes an app role", async () => {
let user = structures.users.user()
user.roles = {
app_test: "role1",
}
const userResponse = await config.createUser(user)
const res = await config.api.roles.remove("app_test")
const updatedUser = await config.api.users.getUser(userResponse._id!)
expect(updatedUser.body.roles).not.toHaveProperty("app_test")
expect(res.body.message).toEqual("App role removed from all users")
})
})
}) })

View File

@ -1,4 +1,17 @@
import {
addBaseTemplates,
EmailTemplates,
getTemplates,
} from "../../../../constants/templates"
import {
EmailTemplatePurpose,
TemplateMetadata,
TemplateMetadataNames,
TemplateType,
} from "../../../../constants"
import { TestConfiguration } from "../../../../tests" import { TestConfiguration } from "../../../../tests"
import { join } from "path"
import { readStaticFile } from "../../../../../src/utilities/fileSystem"
// TODO // TODO
@ -18,18 +31,85 @@ describe("/api/global/template", () => {
}) })
describe("GET /api/global/template/definitions", () => { describe("GET /api/global/template/definitions", () => {
it("retrieves definitions", () => {}) describe("retrieves definitions", () => {
it("checks description definitions", async () => {
let result = await config.api.templates.definitions()
expect(result.body.info[EmailTemplatePurpose.BASE].description).toEqual(
TemplateMetadata[TemplateType.EMAIL][0].description
)
expect(
result.body.info[EmailTemplatePurpose.PASSWORD_RECOVERY].description
).toEqual(TemplateMetadata[TemplateType.EMAIL][1].description)
expect(
result.body.info[EmailTemplatePurpose.WELCOME].description
).toEqual(TemplateMetadata[TemplateType.EMAIL][2].description)
expect(
result.body.info[EmailTemplatePurpose.INVITATION].description
).toEqual(TemplateMetadata[TemplateType.EMAIL][3].description)
expect(
result.body.info[EmailTemplatePurpose.CUSTOM].description
).toEqual(TemplateMetadata[TemplateType.EMAIL][4].description)
}) })
describe("POST /api/global/template", () => {}) it("checks description bindings", async () => {
let result = await config.api.templates.definitions()
describe("GET /api/global/template", () => {}) expect(result.body.bindings[EmailTemplatePurpose.BASE]).toEqual(
TemplateMetadata[TemplateType.EMAIL][0].bindings
)
expect(
result.body.bindings[EmailTemplatePurpose.PASSWORD_RECOVERY]
).toEqual(TemplateMetadata[TemplateType.EMAIL][1].bindings)
expect(result.body.bindings[EmailTemplatePurpose.WELCOME]).toEqual(
TemplateMetadata[TemplateType.EMAIL][2].bindings
)
expect(result.body.bindings[EmailTemplatePurpose.INVITATION]).toEqual(
TemplateMetadata[TemplateType.EMAIL][3].bindings
)
expect(result.body.bindings[EmailTemplatePurpose.CUSTOM]).toEqual(
TemplateMetadata[TemplateType.EMAIL][4].bindings
)
})
})
})
describe("GET /api/global/template/:type", () => {}) describe("POST /api/global/template", () => {
it("adds a new template", async () => {
let purpose = "base"
let contents = "Test contents"
let updatedTemplate = {
contents: contents,
purpose: purpose,
type: "email",
}
await config.api.templates.saveTemplate(updatedTemplate)
let res = await config.api.templates.getTemplate()
let newTemplate = res.body.find((t: any) => (t.purpose = purpose))
expect(newTemplate.contents).toEqual(contents)
})
})
describe("GET /api/global/template/:ownerId", () => {}) describe("GET /api/global/template", () => {
it("fetches templates", async () => {
describe("GET /api/global/template/:id", () => {}) let res = await config.api.templates.getTemplate()
expect(
describe("DELETE /api/global/template/:id/:rev", () => {}) res.body.find((t: any) => t.purpose === EmailTemplatePurpose.BASE)
).toBeDefined()
expect(
res.body.find((t: any) => t.purpose === EmailTemplatePurpose.CUSTOM)
).toBeDefined()
expect(
res.body.find((t: any) => t.purpose === EmailTemplatePurpose.INVITATION)
).toBeDefined()
expect(
res.body.find(
(t: any) => t.purpose === EmailTemplatePurpose.PASSWORD_RECOVERY
)
).toBeDefined()
expect(
res.body.find((t: any) => t.purpose === EmailTemplatePurpose.WELCOME)
).toBeDefined()
})
})
}) })

View File

@ -116,7 +116,7 @@ describe("/api/global/users", () => {
it("should ignore users existing in other tenants", async () => { it("should ignore users existing in other tenants", async () => {
const user = await config.createUser() const user = await config.createUser()
jest.resetAllMocks() jest.clearAllMocks()
await tenancy.doInTenant(TENANT_1, async () => { await tenancy.doInTenant(TENANT_1, async () => {
const response = await config.api.users.bulkCreateUsers([user]) const response = await config.api.users.bulkCreateUsers([user])
@ -229,7 +229,7 @@ describe("/api/global/users", () => {
it("should not be able to create user that exists in other tenant", async () => { it("should not be able to create user that exists in other tenant", async () => {
const user = await config.createUser() const user = await config.createUser()
jest.resetAllMocks() jest.clearAllMocks()
await tenancy.doInTenant(TENANT_1, async () => { await tenancy.doInTenant(TENANT_1, async () => {
delete user._id delete user._id

View File

@ -27,6 +27,14 @@ export enum EmailTemplatePurpose {
CUSTOM = "custom", CUSTOM = "custom",
} }
export enum TemplateMetadataNames {
BASE = "Base format",
PASSWORD_RECOVERY = "Password recovery",
WELCOME = "User welcome",
INVITATION = "User invitation",
CUSTOM = "Custom",
}
export enum InternalTemplateBinding { export enum InternalTemplateBinding {
PLATFORM_URL = "platformUrl", PLATFORM_URL = "platformUrl",
COMPANY = "company", COMPANY = "company",
@ -93,7 +101,7 @@ export const TemplateBindings = {
export const TemplateMetadata = { export const TemplateMetadata = {
[TemplateType.EMAIL]: [ [TemplateType.EMAIL]: [
{ {
name: "Base format", name: TemplateMetadataNames.BASE,
description: description:
"This is the base template, all others are based on it. The {{ body }} will be replaced with another email template.", "This is the base template, all others are based on it. The {{ body }} will be replaced with another email template.",
category: "miscellaneous", category: "miscellaneous",
@ -110,7 +118,7 @@ export const TemplateMetadata = {
], ],
}, },
{ {
name: "Password recovery", name: TemplateMetadataNames.PASSWORD_RECOVERY,
description: description:
"When a user requests a password reset they will receive an email built with this template.", "When a user requests a password reset they will receive an email built with this template.",
category: "user management", category: "user management",
@ -129,7 +137,7 @@ export const TemplateMetadata = {
], ],
}, },
{ {
name: "User welcome", name: TemplateMetadataNames.WELCOME,
description: description:
"When a new user is added they will be sent a welcome email using this template.", "When a new user is added they will be sent a welcome email using this template.",
category: "user management", category: "user management",
@ -137,7 +145,7 @@ export const TemplateMetadata = {
bindings: [], bindings: [],
}, },
{ {
name: "User invitation", name: TemplateMetadataNames.INVITATION,
description: description:
"When inviting a user via the email on-boarding this template will be used.", "When inviting a user via the email on-boarding this template will be used.",
category: "user management", category: "user management",
@ -156,7 +164,7 @@ export const TemplateMetadata = {
], ],
}, },
{ {
name: "Custom", name: TemplateMetadataNames.CUSTOM,
description: description:
"A custom template, this is currently used for SMTP email actions in automations.", "A custom template, this is currently used for SMTP email actions in automations.",
category: "automations", category: "automations",

View File

@ -1,4 +1,12 @@
import "./mocks" import mocks from "./mocks"
// init the licensing mock
import * as pro from "@budibase/pro"
mocks.licenses.init(pro)
// use unlimited license by default
mocks.licenses.useUnlimited()
import * as dbConfig from "../db" import * as dbConfig from "../db"
dbConfig.init() dbConfig.init()
import env from "../environment" import env from "../environment"

View File

@ -0,0 +1,26 @@
import { UserGroup } from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
export class GroupsAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
saveGroup = (group: UserGroup) => {
return this.request
.post(`/api/global/groups`)
.send(group)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
deleteGroup = (id: string, rev: string) => {
return this.request
.delete(`/api/global/groups/${id}/${rev}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
}

View File

@ -10,7 +10,10 @@ import { MigrationAPI } from "./migrations"
import { StatusAPI } from "./status" import { StatusAPI } from "./status"
import { RestoreAPI } from "./restore" import { RestoreAPI } from "./restore"
import { TenantAPI } from "./tenants" import { TenantAPI } from "./tenants"
import { GroupsAPI } from "./groups"
import { RolesAPI } from "./roles"
import { TemplatesAPI } from "./templates"
import { LicenseAPI } from "./license"
export default class API { export default class API {
accounts: AccountAPI accounts: AccountAPI
auth: AuthAPI auth: AuthAPI
@ -23,6 +26,10 @@ export default class API {
status: StatusAPI status: StatusAPI
restore: RestoreAPI restore: RestoreAPI
tenants: TenantAPI tenants: TenantAPI
groups: GroupsAPI
roles: RolesAPI
templates: TemplatesAPI
license: LicenseAPI
constructor(config: TestConfiguration) { constructor(config: TestConfiguration) {
this.accounts = new AccountAPI(config) this.accounts = new AccountAPI(config)
@ -36,5 +43,9 @@ export default class API {
this.status = new StatusAPI(config) this.status = new StatusAPI(config)
this.restore = new RestoreAPI(config) this.restore = new RestoreAPI(config)
this.tenants = new TenantAPI(config) this.tenants = new TenantAPI(config)
this.groups = new GroupsAPI(config)
this.roles = new RolesAPI(config)
this.templates = new TemplatesAPI(config)
this.license = new LicenseAPI(config)
} }
} }

View File

@ -0,0 +1,17 @@
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
export class LicenseAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
activate = async (licenseKey: string) => {
return this.request
.post("/api/global/license/activate")
.send({ licenseKey: licenseKey })
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
}

View File

@ -0,0 +1,32 @@
import TestConfiguration from "../TestConfiguration"
import { TestAPI, TestAPIOpts } from "./base"
export class RolesAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
get = (opts?: TestAPIOpts) => {
return this.request
.get(`/api/global/roles`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(opts?.status ? opts.status : 200)
}
find = (appId: string, opts?: TestAPIOpts) => {
return this.request
.get(`/api/global/roles/${appId}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(opts?.status ? opts.status : 200)
}
remove = (appId: string, opts?: TestAPIOpts) => {
return this.request
.delete(`/api/global/roles/${appId}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(opts?.status ? opts.status : 200)
}
}

View File

@ -0,0 +1,30 @@
import TestConfiguration from "../TestConfiguration"
import { TestAPI, TestAPIOpts } from "./base"
export class TemplatesAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
definitions = (opts?: TestAPIOpts) => {
return this.request
.get(`/api/global/template/definitions`)
.set(opts?.headers ? opts.headers : this.config.defaultHeaders())
.expect(opts?.status ? opts.status : 200)
}
getTemplate = (opts?: TestAPIOpts) => {
return this.request
.get(`/api/global/template`)
.set(opts?.headers ? opts.headers : this.config.defaultHeaders())
.expect(opts?.status ? opts.status : 200)
}
saveTemplate = (data: any, opts?: TestAPIOpts) => {
return this.request
.post(`/api/global/template`)
.send(data)
.set(opts?.headers ? opts.headers : this.config.defaultHeaders())
.expect(opts?.status ? opts.status : 200)
}
}

View File

@ -1,7 +1,7 @@
const email = require("./email") const email = require("./email")
import { mocks as coreMocks } from "@budibase/backend-core/tests" import { mocks } from "@budibase/backend-core/tests"
export = { export = {
email, email,
...coreMocks, ...mocks,
} }

View File

@ -4,7 +4,7 @@ export const UserGroup = () => {
color: "var(--spectrum-global-color-blue-600)", color: "var(--spectrum-global-color-blue-600)",
icon: "UserGroup", icon: "UserGroup",
name: "New group", name: "New group",
roles: {}, roles: { app_uuid1: "ADMIN", app_uuid2: "POWER" },
users: [], users: [],
} }
return group return group

View File

@ -10,7 +10,7 @@ export const user = (userProps?: any): User => {
return { return {
email: newEmail(), email: newEmail(),
password: "test", password: "test",
roles: {}, roles: { app_test: "admin" },
...userProps, ...userProps,
} }
} }

View File

@ -25,7 +25,6 @@
"package.json" "package.json"
], ],
"exclude": [ "exclude": [
"node_modules",
"dist" "dist"
] ]
} }

View File

@ -470,12 +470,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.1.46-alpha.3": "@budibase/backend-core@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.3.tgz#f8caf2af9a8d3a16d4c4280f365567581f9b55a2" resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.6.tgz#eb24abae6e3f6435a01b97978d25a466b672caff"
integrity sha512-osyuJq9db0DeUkaj4uANzo1mMt7SuKO5vSBITemLua0K8T8Z4r2ypE4muktEsfBdPxAH4cclMg/JaYl4RM8bwQ== integrity sha512-oDPhUE1nPoBu74lWQFj+9p8Fxh42CbNiE+PqaIBrcjpgSmg88Ftcr82UHg3YPQSXGBa/7hVvIkyXqVYzhIfG/Q==
dependencies: dependencies:
"@budibase/types" "2.1.46-alpha.3" "@budibase/types" "2.1.46-alpha.6"
"@shopify/jest-koa-mocks" "5.0.1" "@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2" "@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0" aws-sdk "2.1030.0"
@ -507,22 +507,22 @@
uuid "8.3.2" uuid "8.3.2"
zlib "1.0.5" zlib "1.0.5"
"@budibase/pro@2.1.46-alpha.3": "@budibase/pro@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.3.tgz#88e13775402561f1bd8d20483493a34082a6d8ab" resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.6.tgz#c81465fe03c1a2dac69308ce5304e423bfbcabf4"
integrity sha512-B3z/Jk4g1ig8Wx62KmjAeYeITePxwrLHnSoy/Ugz6APNfNiXe7Y/ilQ5BFHWB0z/z3/8Vs1sOdP5c3/R5LpqDQ== integrity sha512-76/29biUDsGfOE4nzMHuVyzTpXPXsNOSe1dkbhGvxBVn42CQGIaR17a+0do9XX5I9qn7zhFJmz2B3UYYb9rZ4g==
dependencies: dependencies:
"@budibase/backend-core" "2.1.46-alpha.3" "@budibase/backend-core" "2.1.46-alpha.6"
"@budibase/types" "2.1.46-alpha.3" "@budibase/types" "2.1.46-alpha.6"
"@koa/router" "8.0.8" "@koa/router" "8.0.8"
bull "4.10.1" bull "4.10.1"
joi "17.6.0" joi "17.6.0"
node-fetch "^2.6.1" node-fetch "^2.6.1"
"@budibase/types@2.1.46-alpha.3": "@budibase/types@2.1.46-alpha.6":
version "2.1.46-alpha.3" version "2.1.46-alpha.6"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.3.tgz#ffd96e1f3b006af5f0c0900e927d0454a2e61c53" resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.6.tgz#d80f47aa57ffa0685f03f5aaf5477d1e985fc9cf"
integrity sha512-JIO5qH/UYbIays/3dDovltiUEL3a4npXZIMlGgARzPQ5DW7ZB8hfJ5fXPt+BsbMXeaJAEsRbDkx82MDQs4y5Lg== integrity sha512-ol0/j0h5A6ZCQrc+qGkigFcuQ8EsyTLhHEhBynh/TWyTbjbUWPJBGTeY5lYzWD2bqQWnRDXsDP4iNdpbuviZNA==
"@cspotcode/source-map-support@^0.8.0": "@cspotcode/source-map-support@^0.8.0":
version "0.8.1" version "0.8.1"

View File

@ -1,25 +1,30 @@
echo "Linking backend-core" echo "Linking backend-core"
cd packages/backend-core cd packages/backend-core
yarn unlink
yarn link yarn link
cd - cd -
echo "Linking string-templates" echo "Linking string-templates"
cd packages/string-templates cd packages/string-templates
yarn unlink
yarn link yarn link
cd - cd -
echo "Linking types" echo "Linking types"
cd packages/types cd packages/types
yarn unlink
yarn link yarn link
cd - cd -
echo "Linking bbui" echo "Linking bbui"
cd packages/bbui cd packages/bbui
yarn unlink
yarn link yarn link
cd - cd -
echo "Linking frontend-core" echo "Linking frontend-core"
cd packages/frontend-core cd packages/frontend-core
yarn unlink
yarn link yarn link
cd - cd -
@ -30,6 +35,7 @@ if [ -d "../budibase-pro" ]; then
cd packages/pro cd packages/pro
echo "Linking pro" echo "Linking pro"
yarn unlink
yarn link yarn link
echo "Linking backend-core to pro" echo "Linking backend-core to pro"