budibase/packages/server/src/tests/utilities/TestConfiguration.js

585 lines
14 KiB
JavaScript
Raw Normal View History

require("../../db").init()
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const env = require("../../environment")
2021-03-03 19:41:49 +01:00
const {
basicTable,
basicRow,
basicRole,
basicAutomation,
2021-03-04 11:05:50 +01:00
basicDatasource,
basicQuery,
2021-03-08 15:49:19 +01:00
basicScreen,
basicLayout,
basicWebhook,
TENANT_ID,
2021-03-03 19:41:49 +01:00
} = require("./structures")
2021-03-04 11:05:50 +01:00
const controllers = require("./controllers")
const supertest = require("supertest")
const { cleanup } = require("../../utilities/fileSystem")
const { Cookies, Headers } = require("@budibase/backend-core/constants")
const { jwt } = require("@budibase/backend-core/auth")
const { doInTenant, doWithGlobalDB } = require("@budibase/backend-core/tenancy")
const { createASession } = require("@budibase/backend-core/sessions")
const { user: userCache } = require("@budibase/backend-core/cache")
const newid = require("../../db/newid")
const context = require("@budibase/backend-core/context")
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
const { encrypt } = require("@budibase/backend-core/encryption")
const { DocumentType } = require("../../db/utils")
const GLOBAL_USER_ID = "us_uuid1"
const EMAIL = "babs@babs.com"
const FIRSTNAME = "Barbara"
const LASTNAME = "Barbington"
2022-01-25 23:54:50 +01:00
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
class TestConfiguration {
constructor(openServer = true) {
if (openServer) {
// use a random port because it doesn't matter
env.PORT = 0
this.server = require("../../app")
// we need the request for logging in, involves cookies, hard to fake
this.request = supertest(this.server)
}
this.appId = null
this.allApps = []
}
getRequest() {
return this.request
}
2022-02-25 16:55:19 +01:00
getApp() {
return this.app
}
2022-08-10 12:01:54 +02:00
getProdApp() {
return this.prodApp
}
getAppId() {
return this.appId
}
2022-04-04 16:59:00 +02:00
getProdAppId() {
return this.prodAppId
}
getUserDetails() {
return {
globalId: GLOBAL_USER_ID,
email: EMAIL,
firstName: FIRSTNAME,
lastName: LASTNAME,
}
}
async doInContext(appId, task) {
if (!appId) {
appId = this.appId
}
return doInTenant(TENANT_ID, () => {
// check if already in a context
if (context.getAppId() == null && appId !== null) {
return context.doInAppContext(appId, async () => {
return task()
})
} else {
return task()
}
})
}
// SETUP / TEARDOWN
// use a new id as the name to avoid name collisions
async init(appName = newid()) {
await this.globalUser()
return this.createApp(appName)
}
end() {
if (!this) {
return
}
if (this.server) {
this.server.close()
}
cleanup(this.allApps.map(app => app.appId))
}
// UTILS
2022-08-10 12:01:54 +02:00
async _req(body, params, controlFunc) {
2022-07-13 14:22:21 +02:00
// create a fake request ctx
const request = {}
2022-08-10 12:01:54 +02:00
const appId = this.appId
2022-07-13 14:22:21 +02:00
request.appId = appId
// fake cookies, we don't need them
request.cookies = { set: () => {}, get: () => {} }
request.config = { jwtSecret: env.JWT_SECRET }
2022-07-13 14:22:21 +02:00
request.user = { appId, tenantId: TENANT_ID }
request.query = {}
request.request = {
2022-07-13 14:22:21 +02:00
body,
}
2022-07-13 14:22:21 +02:00
if (params) {
request.params = params
}
return this.doInContext(appId, async () => {
await controlFunc(request)
return request.body
})
}
// USER / AUTH
async globalUser({
id = GLOBAL_USER_ID,
firstName = FIRSTNAME,
lastName = LASTNAME,
builder = true,
2022-05-20 22:16:29 +02:00
admin = false,
email = EMAIL,
roles,
} = {}) {
return doWithGlobalDB(TENANT_ID, async db => {
let existing
try {
existing = await db.get(id)
} catch (err) {
existing = { email }
}
const user = {
_id: id,
...existing,
roles: roles || {},
tenantId: TENANT_ID,
firstName,
lastName,
}
await createASession(id, {
sessionId: "sessionid",
tenantId: TENANT_ID,
csrfToken: CSRF_TOKEN,
})
if (builder) {
user.builder = { global: true }
} else {
user.builder = { global: false }
}
2022-05-20 22:16:29 +02:00
if (admin) {
user.admin = { global: true }
} else {
user.admin = { global: false }
}
const resp = await db.put(user)
return {
_rev: resp._rev,
...user,
}
2022-01-25 23:54:50 +01:00
})
}
async createUser(
id = null,
firstName = FIRSTNAME,
lastName = LASTNAME,
email = EMAIL,
builder = true,
admin = false,
roles = {}
) {
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
2022-05-20 22:16:29 +02:00
const resp = await this.globalUser({
id: globalId,
firstName,
lastName,
2022-05-20 22:16:29 +02:00
email,
builder,
admin,
roles,
})
await userCache.invalidateUser(globalId)
return {
...resp,
globalId,
}
}
async login({ roleId, userId, builder, prodApp = false } = {}) {
const appId = prodApp ? this.prodAppId : this.appId
return context.doInAppContext(appId, async () => {
userId = !userId ? `us_uuid1` : userId
if (!this.request) {
throw "Server has not been opened, cannot login."
}
// make sure the user exists in the global DB
if (roleId !== BUILTIN_ROLE_IDS.PUBLIC) {
await this.globalUser({
id: userId,
builder,
roles: { [this.prodAppId]: roleId },
})
}
await createASession(userId, {
sessionId: "sessionid",
tenantId: TENANT_ID,
})
// have to fake this
const auth = {
userId,
sessionId: "sessionid",
tenantId: TENANT_ID,
}
const app = {
roleId: roleId,
appId,
}
const authToken = jwt.sign(auth, env.JWT_SECRET)
const appToken = jwt.sign(app, env.JWT_SECRET)
// returning necessary request headers
await userCache.invalidateUser(userId)
return {
Accept: "application/json",
Cookie: [
`${Cookies.Auth}=${authToken}`,
`${Cookies.CurrentApp}=${appToken}`,
],
[Headers.APP_ID]: appId,
}
})
}
defaultHeaders(extras = {}) {
const auth = {
userId: GLOBAL_USER_ID,
2021-07-08 01:30:55 +02:00
sessionId: "sessionid",
tenantId: TENANT_ID,
}
const app = {
roleId: BUILTIN_ROLE_IDS.ADMIN,
appId: this.appId,
}
const authToken = jwt.sign(auth, env.JWT_SECRET)
const appToken = jwt.sign(app, env.JWT_SECRET)
const headers = {
Accept: "application/json",
Cookie: [
`${Cookies.Auth}=${authToken}`,
`${Cookies.CurrentApp}=${appToken}`,
],
2022-01-25 23:54:50 +01:00
[Headers.CSRF_TOKEN]: CSRF_TOKEN,
...extras,
}
if (this.appId) {
2021-07-23 16:29:14 +02:00
headers[Headers.APP_ID] = this.appId
}
return headers
}
publicHeaders({ prodApp = true } = {}) {
const appId = prodApp ? this.prodAppId : this.appId
const headers = {
Accept: "application/json",
}
2021-10-26 17:21:26 +02:00
if (appId) {
headers[Headers.APP_ID] = appId
}
return headers
}
async roleHeaders({
email = EMAIL,
roleId = BUILTIN_ROLE_IDS.ADMIN,
builder = false,
prodApp = true,
} = {}) {
return this.login({ email, roleId, builder, prodApp })
2021-03-08 15:49:19 +01:00
}
// API
async generateApiKey(userId = GLOBAL_USER_ID) {
return doWithGlobalDB(TENANT_ID, async db => {
const id = generateDevInfoID(userId)
let devInfo
try {
devInfo = await db.get(id)
} catch (err) {
devInfo = { _id: id, userId }
}
devInfo.apiKey = encrypt(`${TENANT_ID}${SEPARATOR}${newid()}`)
await db.put(devInfo)
return devInfo.apiKey
})
}
// APP
2022-05-20 22:16:29 +02:00
async createApp(appName) {
// create dev app
// clear any old app
this.appId = null
await context.updateAppId(null)
2021-03-04 11:05:50 +01:00
this.app = await this._req({ name: appName }, null, controllers.app.create)
2021-05-16 22:25:37 +02:00
this.appId = this.app.appId
await context.updateAppId(this.appId)
// create production app
2022-05-20 22:16:29 +02:00
this.prodApp = await this.deploy()
this.allApps.push(this.prodApp)
this.allApps.push(this.app)
return this.app
}
async deploy() {
await this._req(null, null, controllers.deploy.deployApp)
const prodAppId = this.getAppId().replace("_dev", "")
2022-07-13 14:22:21 +02:00
this.prodAppId = prodAppId
2022-08-10 12:01:54 +02:00
return context.doInAppContext(prodAppId, async () => {
2022-08-10 12:01:54 +02:00
const db = context.getProdAppDB()
return await db.get(DocumentType.APP_METADATA)
})
}
// TABLE
async updateTable(config = null) {
config = config || basicTable()
2021-03-04 11:05:50 +01:00
this.table = await this._req(config, null, controllers.table.save)
return this.table
}
async createTable(config = null) {
if (config != null && config._id) {
delete config._id
}
return this.updateTable(config)
}
2021-03-04 15:36:59 +01:00
async getTable(tableId = null) {
tableId = tableId || this.table._id
2022-02-28 19:53:03 +01:00
return this._req(null, { tableId }, controllers.table.find)
2021-03-04 15:36:59 +01:00
}
async createLinkedTable(relationshipType = null, links = ["link"]) {
2021-03-04 14:07:33 +01:00
if (!this.table) {
throw "Must have created a table first."
}
const tableConfig = basicTable()
tableConfig.primaryDisplay = "name"
for (let link of links) {
tableConfig.schema[link] = {
type: "link",
fieldName: link,
tableId: this.table._id,
name: link,
}
if (relationshipType) {
tableConfig.schema[link].relationshipType = relationshipType
}
}
2021-03-04 14:07:33 +01:00
const linkedTable = await this.createTable(tableConfig)
this.linkedTable = linkedTable
return linkedTable
}
async createAttachmentTable() {
const table = basicTable()
table.schema.attachment = {
type: "attachment",
}
return this.createTable(table)
}
// ROW
async createRow(config = null) {
if (!this.table) {
throw "Test requires table to be configured."
}
const tableId = (config && config.tableId) || this.table._id
config = config || basicRow(tableId)
return this._req(config, { tableId }, controllers.row.save)
}
async getRow(tableId, rowId) {
return this._req(null, { tableId, rowId }, controllers.row.find)
}
async getRows(tableId) {
if (!tableId && this.table) {
tableId = this.table._id
}
2021-06-17 17:35:58 +02:00
return this._req(null, { tableId }, controllers.row.fetch)
}
// ROLE
async createRole(config = null) {
config = config || basicRole()
2021-03-04 11:05:50 +01:00
return this._req(config, null, controllers.role.save)
}
async addPermission(roleId, resourceId, level = "read") {
return this._req(
null,
{
roleId,
resourceId,
level,
},
2021-03-04 11:05:50 +01:00
controllers.perms.addPermission
)
}
// VIEW
async createView(config) {
if (!this.table) {
throw "Test requires table to be configured."
}
const view = config || {
tableId: this.table._id,
name: "ViewTest",
}
2021-03-04 11:05:50 +01:00
return this._req(view, null, controllers.view.save)
}
// AUTOMATION
2021-03-03 19:41:49 +01:00
async createAutomation(config) {
config = config || basicAutomation()
if (config._rev) {
delete config._rev
}
this.automation = (
2021-03-04 11:05:50 +01:00
await this._req(config, null, controllers.automation.create)
2021-03-03 19:41:49 +01:00
).automation
return this.automation
}
async getAllAutomations() {
2021-03-04 11:05:50 +01:00
return this._req(null, null, controllers.automation.fetch)
2021-03-03 19:41:49 +01:00
}
2021-03-04 11:05:50 +01:00
async deleteAutomation(automation = null) {
2021-03-03 19:41:49 +01:00
automation = automation || this.automation
if (!automation) {
return
}
return this._req(
null,
{ id: automation._id, rev: automation._rev },
2021-03-04 11:05:50 +01:00
controllers.automation.destroy
2021-03-03 19:41:49 +01:00
)
}
async createWebhook(config = null) {
if (!this.automation) {
throw "Must create an automation before creating webhook."
}
config = config || basicWebhook(this.automation._id)
return (await this._req(config, null, controllers.webhook.save)).webhook
}
// DATASOURCE
2021-03-04 11:05:50 +01:00
async createDatasource(config = null) {
config = config || basicDatasource()
2021-10-27 14:10:46 +02:00
const response = await this._req(config, null, controllers.datasource.save)
this.datasource = response.datasource
2021-03-04 11:05:50 +01:00
return this.datasource
}
async updateDatasource(datasource) {
const response = await this._req(
datasource,
{ datasourceId: datasource._id },
controllers.datasource.update
)
this.datasource = response.datasource
return this.datasource
}
async restDatasource(cfg) {
return this.createDatasource({
datasource: {
...basicDatasource().datasource,
source: "REST",
config: cfg || {},
},
})
}
async dynamicVariableDatasource() {
let datasource = await this.restDatasource()
const basedOnQuery = await this.createQuery({
...basicQuery(datasource._id),
fields: {
path: "www.google.com",
},
})
datasource = await this.updateDatasource({
...datasource,
config: {
dynamicVariables: [
{
queryId: basedOnQuery._id,
name: "variable3",
value: "{{ data.0.[value] }}",
},
],
},
})
return { datasource, query: basedOnQuery }
}
// QUERY
async previewQuery(request, config, datasource, fields, params, verb) {
return request
.post(`/api/queries/preview`)
.send({
datasourceId: datasource._id,
parameters: params || {},
fields,
queryVerb: verb || "read",
2022-02-11 17:28:19 +01:00
name: datasource.name,
})
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
2021-03-04 11:05:50 +01:00
async createQuery(config = null) {
if (!this.datasource && !config) {
throw "No datasource created for query."
2021-03-04 11:05:50 +01:00
}
config = config || basicQuery(this.datasource._id)
return this._req(config, null, controllers.query.save)
}
// SCREEN
2021-03-08 15:49:19 +01:00
async createScreen(config = null) {
config = config || basicScreen()
return this._req(config, null, controllers.screen.save)
}
// LAYOUT
async createLayout(config = null) {
config = config || basicLayout()
return await this._req(config, null, controllers.layout.save)
}
}
module.exports = TestConfiguration