Merge pull request #13146 from Budibase/budi-7710-user-groups-do-not-fully-support-custom-roles

Properly type TestConfiguration.ts
This commit is contained in:
Sam Rose 2024-02-28 16:56:17 +00:00 committed by GitHub
commit af44a973b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 295 additions and 216 deletions

View File

@ -74,7 +74,7 @@ export function getGlobalIDFromUserMetadataID(id: string) {
* Generates a template ID.
* @param ownerId The owner/user of the template, this could be global or a workspace level.
*/
export function generateTemplateID(ownerId: any) {
export function generateTemplateID(ownerId: string) {
return `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
}
@ -105,7 +105,7 @@ export function prefixRoleID(name: string) {
* Generates a new dev info document ID - this is scoped to a user.
* @returns The new dev info ID which info for dev (like api key) can be stored under.
*/
export const generateDevInfoID = (userId: any) => {
export const generateDevInfoID = (userId: string) => {
return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
}

View File

@ -20,6 +20,7 @@ import {
AutomationActionStepId,
AutomationResults,
UserCtx,
DeleteAutomationResponse,
} from "@budibase/types"
import { getActionDefinitions as actionDefs } from "../../automations/actions"
import sdk from "../../sdk"
@ -72,7 +73,9 @@ function cleanAutomationInputs(automation: Automation) {
return automation
}
export async function create(ctx: UserCtx) {
export async function create(
ctx: UserCtx<Automation, { message: string; automation: Automation }>
) {
const db = context.getAppDB()
let automation = ctx.request.body
automation.appId = ctx.appId
@ -207,7 +210,7 @@ export async function find(ctx: UserCtx) {
ctx.body = await db.get(ctx.params.id)
}
export async function destroy(ctx: UserCtx) {
export async function destroy(ctx: UserCtx<void, DeleteAutomationResponse>) {
const db = context.getAppDB()
const automationId = ctx.params.id
const oldAutomation = await db.get<Automation>(automationId)

View File

@ -1,9 +1,17 @@
import { EMPTY_LAYOUT } from "../../constants/layouts"
import { generateLayoutID, getScreenParams } from "../../db/utils"
import { events, context } from "@budibase/backend-core"
import { BBContext, Layout } from "@budibase/types"
import {
BBContext,
Layout,
SaveLayoutRequest,
SaveLayoutResponse,
UserCtx,
} from "@budibase/types"
export async function save(ctx: BBContext) {
export async function save(
ctx: UserCtx<SaveLayoutRequest, SaveLayoutResponse>
) {
const db = context.getAppDB()
let layout = ctx.request.body

View File

@ -73,7 +73,7 @@ const _import = async (ctx: UserCtx) => {
}
export { _import as import }
export async function save(ctx: UserCtx) {
export async function save(ctx: UserCtx<Query, Query>) {
const db = context.getAppDB()
const query: Query = ctx.request.body

View File

@ -7,7 +7,13 @@ import {
roles,
} from "@budibase/backend-core"
import { updateAppPackage } from "./application"
import { Plugin, ScreenProps, BBContext, Screen } from "@budibase/types"
import {
Plugin,
ScreenProps,
BBContext,
Screen,
UserCtx,
} from "@budibase/types"
import { builderSocket } from "../../websockets"
export async function fetch(ctx: BBContext) {
@ -31,7 +37,7 @@ export async function fetch(ctx: BBContext) {
)
}
export async function save(ctx: BBContext) {
export async function save(ctx: UserCtx<Screen, Screen>) {
const db = context.getAppDB()
let screen = ctx.request.body

View File

@ -394,7 +394,7 @@ describe("/automations", () => {
it("deletes a automation by its ID", async () => {
const automation = await config.createAutomation()
const res = await request
.delete(`/api/automations/${automation.id}/${automation.rev}`)
.delete(`/api/automations/${automation._id}/${automation._rev}`)
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
@ -408,7 +408,7 @@ describe("/automations", () => {
await checkBuilderEndpoint({
config,
method: "DELETE",
url: `/api/automations/${automation.id}/${automation._rev}`,
url: `/api/automations/${automation._id}/${automation._rev}`,
})
})
})

View File

@ -44,7 +44,7 @@ describe("/backups", () => {
expect(headers["content-disposition"]).toEqual(
`attachment; filename="${
config.getApp()!.name
config.getApp().name
}-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
)
})

View File

@ -86,7 +86,7 @@ describe("/datasources", () => {
})
// check variables in cache
let contents = await checkCacheForDynamicVariable(
query._id,
query._id!,
"variable3"
)
expect(contents.rows.length).toEqual(1)
@ -102,7 +102,7 @@ describe("/datasources", () => {
expect(res.body.errors).toBeUndefined()
// check variables no longer in cache
contents = await checkCacheForDynamicVariable(query._id, "variable3")
contents = await checkCacheForDynamicVariable(query._id!, "variable3")
expect(contents).toBe(null)
})
})

View File

@ -467,7 +467,10 @@ describe("/queries", () => {
queryString: "test={{ variable3 }}",
})
// check its in cache
const contents = await checkCacheForDynamicVariable(base._id, "variable3")
const contents = await checkCacheForDynamicVariable(
base._id!,
"variable3"
)
expect(contents.rows.length).toEqual(1)
const responseBody = await preview(datasource, {
path: "www.failonce.com",
@ -490,7 +493,7 @@ describe("/queries", () => {
queryString: "test={{ variable3 }}",
})
// check its in cache
let contents = await checkCacheForDynamicVariable(base._id, "variable3")
let contents = await checkCacheForDynamicVariable(base._id!, "variable3")
expect(contents.rows.length).toEqual(1)
// delete the query
@ -500,7 +503,7 @@ describe("/queries", () => {
.expect(200)
// check variables no longer in cache
contents = await checkCacheForDynamicVariable(base._id, "variable3")
contents = await checkCacheForDynamicVariable(base._id!, "variable3")
expect(contents).toBe(null)
})
})

View File

@ -110,7 +110,7 @@ describe.each([
config.api.row.get(tbl_Id, id, { expectStatus: status })
const getRowUsage = async () => {
const { total } = await config.doInContext(null, () =>
const { total } = await config.doInContext(undefined, () =>
quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS)
)
return total

View File

@ -27,15 +27,17 @@ describe("/users", () => {
describe("fetch", () => {
it("returns a list of users from an instance db", async () => {
await config.createUser({ id: "uuidx" })
await config.createUser({ id: "uuidy" })
const id1 = `us_${utils.newid()}`
const id2 = `us_${utils.newid()}`
await config.createUser({ _id: id1 })
await config.createUser({ _id: id2 })
const res = await config.api.user.fetch()
expect(res.length).toBe(3)
const ids = res.map(u => u._id)
expect(ids).toContain(`ro_ta_users_us_uuidx`)
expect(ids).toContain(`ro_ta_users_us_uuidy`)
expect(ids).toContain(`ro_ta_users_${id1}`)
expect(ids).toContain(`ro_ta_users_${id2}`)
})
it("should apply authorization to endpoint", async () => {
@ -54,7 +56,7 @@ describe("/users", () => {
describe("update", () => {
it("should be able to update the user", async () => {
const user: UserMetadata = await config.createUser({
id: `us_update${utils.newid()}`,
_id: `us_update${utils.newid()}`,
})
user.roleId = roles.BUILTIN_ROLE_IDS.BASIC
delete user._rev

View File

@ -4,6 +4,7 @@ import { AppStatus } from "../../../../db/utils"
import { roles, tenancy, context, db } from "@budibase/backend-core"
import env from "../../../../environment"
import Nano from "@budibase/nano"
import TestConfiguration from "src/tests/utilities/TestConfiguration"
class Request {
appId: any
@ -52,10 +53,10 @@ export const clearAllApps = async (
})
}
export const clearAllAutomations = async (config: any) => {
export const clearAllAutomations = async (config: TestConfiguration) => {
const automations = await config.getAllAutomations()
for (let auto of automations) {
await context.doInAppContext(config.appId, async () => {
await context.doInAppContext(config.getAppId(), async () => {
await config.deleteAutomation(auto)
})
}
@ -101,7 +102,12 @@ export const checkBuilderEndpoint = async ({
method,
url,
body,
}: any) => {
}: {
config: TestConfiguration
method: string
url: string
body?: any
}) => {
const headers = await config.login({
userId: "us_fail",
builder: false,

View File

@ -36,7 +36,7 @@ describe("/webhooks", () => {
const automation = await config.createAutomation()
const res = await request
.put(`/api/webhooks`)
.send(basicWebhook(automation._id))
.send(basicWebhook(automation._id!))
.set(config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
@ -145,7 +145,7 @@ describe("/webhooks", () => {
let automation = collectAutomation()
let newAutomation = await config.createAutomation(automation)
let syncWebhook = await config.createWebhook(
basicWebhook(newAutomation._id)
basicWebhook(newAutomation._id!)
)
// replicate changes before checking webhook

View File

@ -29,6 +29,6 @@ start().catch(err => {
throw err
})
export function getServer() {
export function getServer(): Server {
return server
}

View File

@ -1,9 +1,11 @@
import { Layout } from "@budibase/types"
export const BASE_LAYOUT_PROP_IDS = {
PRIVATE: "layout_private_master",
PUBLIC: "layout_public_master",
}
export const EMPTY_LAYOUT = {
export const EMPTY_LAYOUT: Layout = {
componentLibraries: ["@budibase/standard-components"],
title: "{{ name }}",
favicon: "./_shared/favicon.png",

View File

@ -1,5 +1,6 @@
import { roles } from "@budibase/backend-core"
import { BASE_LAYOUT_PROP_IDS } from "./layouts"
import { Screen } from "@budibase/types"
export function createHomeScreen(
config: {
@ -9,10 +10,8 @@ export function createHomeScreen(
roleId: roles.BUILTIN_ROLE_IDS.BASIC,
route: "/",
}
) {
): Screen {
return {
description: "",
url: "",
layoutId: BASE_LAYOUT_PROP_IDS.PRIVATE,
props: {
_id: "d834fea2-1b3e-4320-ab34-f9009f5ecc59",

View File

@ -13,7 +13,7 @@ describe("syncApps", () => {
afterAll(config.end)
it("runs successfully", async () => {
return config.doInContext(null, async () => {
return config.doInContext(undefined, async () => {
// create the usage quota doc and mock usages
await quotas.getQuotaUsage()
await quotas.setUsage(3, StaticQuotaName.APPS, QuotaUsageType.STATIC)

View File

@ -12,8 +12,8 @@ describe("syncCreators", () => {
afterAll(config.end)
it("syncs creators", async () => {
return config.doInContext(null, async () => {
await config.createUser({ admin: true })
return config.doInContext(undefined, async () => {
await config.createUser({ admin: { global: true } })
await syncCreators.run()

View File

@ -14,7 +14,7 @@ describe("syncRows", () => {
afterAll(config.end)
it("runs successfully", async () => {
return config.doInContext(null, async () => {
return config.doInContext(undefined, async () => {
// create the usage quota doc and mock usages
await quotas.getQuotaUsage()
await quotas.setUsage(300, StaticQuotaName.ROWS, QuotaUsageType.STATIC)

View File

@ -12,7 +12,7 @@ describe("syncUsers", () => {
afterAll(config.end)
it("syncs users", async () => {
return config.doInContext(null, async () => {
return config.doInContext(undefined, async () => {
await config.createUser()
await syncUsers.run()

View File

@ -40,7 +40,7 @@ describe("migrations", () => {
describe("backfill", () => {
it("runs app db migration", async () => {
await config.doInContext(null, async () => {
await config.doInContext(undefined, async () => {
await clearMigrations()
await config.createAutomation()
await config.createAutomation(structures.newAutomation())
@ -93,18 +93,18 @@ describe("migrations", () => {
})
it("runs global db migration", async () => {
await config.doInContext(null, async () => {
await config.doInContext(undefined, async () => {
await clearMigrations()
const appId = config.prodAppId
const appId = config.getProdAppId()
const roles = { [appId]: "role_12345" }
await config.createUser({
builder: false,
admin: true,
builder: { global: false },
admin: { global: true },
roles,
}) // admin only
await config.createUser({
builder: false,
admin: false,
builder: { global: false },
admin: { global: false },
roles,
}) // non admin non builder
await config.createTable()

View File

@ -43,8 +43,8 @@ async function createUser(email: string, roles: UserRoles, builder?: boolean) {
const user = await config.createUser({
email,
roles,
builder: builder || false,
admin: false,
builder: { global: builder || false },
admin: { global: false },
})
await context.doInContext(config.appId!, async () => {
await events.user.created(user)
@ -55,10 +55,10 @@ async function createUser(email: string, roles: UserRoles, builder?: boolean) {
async function removeUserRole(user: User) {
const final = await config.globalUser({
...user,
id: user._id,
_id: user._id,
roles: {},
builder: false,
admin: false,
builder: { global: false },
admin: { global: false },
})
await context.doInContext(config.appId!, async () => {
await events.user.updated(final)
@ -69,8 +69,8 @@ async function createGroupAndUser(email: string) {
groupUser = await config.createUser({
email,
roles: {},
builder: false,
admin: false,
builder: { global: false },
admin: { global: false },
})
group = await config.createGroup()
await config.addUserToGroup(group._id!, groupUser._id!)

View File

@ -81,7 +81,7 @@ describe("sdk >> rows >> internal", () => {
const response = await internalSdk.save(
table._id!,
row,
config.user._id
config.getUser()._id
)
expect(response).toEqual({
@ -129,7 +129,7 @@ describe("sdk >> rows >> internal", () => {
const response = await internalSdk.save(
table._id!,
row,
config.user._id
config.getUser()._id
)
expect(response).toEqual({
@ -190,15 +190,15 @@ describe("sdk >> rows >> internal", () => {
await config.doInContext(config.appId, async () => {
for (const row of makeRows(5)) {
await internalSdk.save(table._id!, row, config.user._id)
await internalSdk.save(table._id!, row, config.getUser()._id)
}
await Promise.all(
makeRows(10).map(row =>
internalSdk.save(table._id!, row, config.user._id)
internalSdk.save(table._id!, row, config.getUser()._id)
)
)
for (const row of makeRows(5)) {
await internalSdk.save(table._id!, row, config.user._id)
await internalSdk.save(table._id!, row, config.getUser()._id)
}
})

View File

@ -22,15 +22,18 @@ describe("syncGlobalUsers", () => {
expect(metadata).toHaveLength(1)
expect(metadata).toEqual([
expect.objectContaining({
_id: db.generateUserMetadataID(config.user._id),
_id: db.generateUserMetadataID(config.getUser()._id!),
}),
])
})
})
it("admin and builders users are synced", async () => {
const user1 = await config.createUser({ admin: true })
const user2 = await config.createUser({ admin: false, builder: true })
const user1 = await config.createUser({ admin: { global: true } })
const user2 = await config.createUser({
admin: { global: false },
builder: { global: true },
})
await config.doInContext(config.appId, async () => {
expect(await rawUserMetadata()).toHaveLength(1)
await syncGlobalUsers()
@ -51,7 +54,10 @@ describe("syncGlobalUsers", () => {
})
it("app users are not synced if not specified", async () => {
const user = await config.createUser({ admin: false, builder: false })
const user = await config.createUser({
admin: { global: false },
builder: { global: false },
})
await config.doInContext(config.appId, async () => {
await syncGlobalUsers()
@ -68,8 +74,14 @@ describe("syncGlobalUsers", () => {
it("app users are added when group is assigned to app", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
const user1 = await config.createUser({
admin: { global: false },
builder: { global: false },
})
const user2 = await config.createUser({
admin: { global: false },
builder: { global: false },
})
await proSdk.groups.addUsers(group.id, [user1._id!, user2._id!])
await config.doInContext(config.appId, async () => {
@ -103,8 +115,14 @@ describe("syncGlobalUsers", () => {
it("app users are removed when app is removed from user group", async () => {
await config.doInTenant(async () => {
const group = await proSdk.groups.save(structures.userGroups.userGroup())
const user1 = await config.createUser({ admin: false, builder: false })
const user2 = await config.createUser({ admin: false, builder: false })
const user1 = await config.createUser({
admin: { global: false },
builder: { global: false },
})
const user2 = await config.createUser({
admin: { global: false },
builder: { global: false },
})
await proSdk.groups.updateGroupApps(group.id, {
appsToAdd: [
{ appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },

View File

@ -49,25 +49,31 @@ import {
AuthToken,
Automation,
CreateViewRequest,
Ctx,
Datasource,
FieldType,
INTERNAL_TABLE_SOURCE_ID,
Layout,
Query,
RelationshipFieldMetadata,
RelationshipType,
Row,
Screen,
SearchParams,
SourceName,
Table,
TableSourceType,
User,
UserRoles,
UserCtx,
View,
Webhook,
WithRequired,
} from "@budibase/types"
import API from "./api"
import { cloneDeep } from "lodash"
import jwt, { Secret } from "jsonwebtoken"
import { Server } from "http"
mocks.licenses.init(pro)
@ -82,27 +88,23 @@ export interface TableToBuild extends Omit<Table, "sourceId" | "sourceType"> {
}
export default class TestConfiguration {
server: any
request: supertest.SuperTest<supertest.Test> | undefined
server?: Server
request?: supertest.SuperTest<supertest.Test>
started: boolean
appId: string | null
allApps: any[]
appId?: string
allApps: App[]
app?: App
prodApp: any
prodAppId: any
user: any
userMetadataId: any
prodApp?: App
prodAppId?: string
user?: User
userMetadataId?: string
table?: Table
automation: any
automation?: Automation
datasource?: Datasource
tenantId?: string
api: API
csrfToken?: string
private get globalUserId() {
return this.user._id
}
constructor(openServer = true) {
if (openServer) {
// use a random port because it doesn't matter
@ -114,7 +116,7 @@ export default class TestConfiguration {
} else {
this.started = false
}
this.appId = null
this.appId = undefined
this.allApps = []
this.api = new API(this)
@ -125,46 +127,86 @@ export default class TestConfiguration {
}
getApp() {
if (!this.app) {
throw new Error("app has not been initialised, call config.init() first")
}
return this.app
}
getProdApp() {
if (!this.prodApp) {
throw new Error(
"prodApp has not been initialised, call config.init() first"
)
}
return this.prodApp
}
getAppId() {
if (!this.appId) {
throw "appId has not been initialised properly"
throw new Error(
"appId has not been initialised, call config.init() first"
)
}
return this.appId
}
getProdAppId() {
if (!this.prodAppId) {
throw new Error(
"prodAppId has not been initialised, call config.init() first"
)
}
return this.prodAppId
}
getUser(): User {
if (!this.user) {
throw new Error("User has not been initialised, call config.init() first")
}
return this.user
}
getUserDetails() {
const user = this.getUser()
return {
globalId: this.globalUserId,
email: this.user.email,
firstName: this.user.firstName,
lastName: this.user.lastName,
globalId: user._id!,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
}
}
getAutomation() {
if (!this.automation) {
throw new Error(
"automation has not been initialised, call config.init() first"
)
}
return this.automation
}
getDatasource() {
if (!this.datasource) {
throw new Error(
"datasource has not been initialised, call config.init() first"
)
}
return this.datasource
}
async doInContext<T>(
appId: string | null,
appId: string | undefined,
task: () => Promise<T>
): Promise<T> {
const tenant = this.getTenantId()
return tenancy.doInTenant(tenant, () => {
if (!appId) {
appId = this.appId
}
const tenant = this.getTenantId()
return tenancy.doInTenant(tenant, () => {
// check if already in a context
if (context.getAppId() == null && appId !== null) {
if (context.getAppId() == null && appId) {
return context.doInAppContext(appId, async () => {
return task()
})
@ -259,7 +301,11 @@ export default class TestConfiguration {
// UTILS
_req(body: any, params: any, controlFunc: any) {
_req<Req extends Record<string, any> | void, Res>(
handler: (ctx: UserCtx<Req, Res>) => Promise<void>,
body?: Req,
params?: Record<string, string | undefined>
): Promise<Res> {
// create a fake request ctx
const request: any = {}
const appId = this.appId
@ -278,63 +324,48 @@ export default class TestConfiguration {
throw new Error(`Error ${status} - ${message}`)
}
return this.doInContext(appId, async () => {
await controlFunc(request)
await handler(request)
return request.body
})
}
// USER / AUTH
async globalUser(
config: {
id?: string
firstName?: string
lastName?: string
builder?: boolean
admin?: boolean
email?: string
roles?: any
} = {}
): Promise<User> {
async globalUser(config: Partial<User> = {}): Promise<User> {
const {
id = `us_${newid()}`,
_id = `us_${newid()}`,
firstName = generator.first(),
lastName = generator.last(),
builder = true,
admin = false,
builder = { global: true },
admin = { global: false },
email = generator.email(),
roles,
tenantId = this.getTenantId(),
roles = {},
} = config
const db = tenancy.getTenantDB(this.getTenantId())
let existing
let existing: Partial<User> = {}
try {
existing = await db.get<any>(id)
existing = await db.get<User>(_id)
} catch (err) {
existing = { email }
// ignore
}
const user: User = {
_id: id,
_id,
...existing,
roles: roles || {},
tenantId: this.getTenantId(),
...config,
email,
roles,
tenantId,
firstName,
lastName,
builder,
admin,
}
await sessions.createASession(id, {
await sessions.createASession(_id, {
sessionId: "sessionid",
tenantId: this.getTenantId(),
csrfToken: this.csrfToken,
})
if (builder) {
user.builder = { global: true }
} else {
user.builder = { global: false }
}
if (admin) {
user.admin = { global: true }
} else {
user.admin = { global: false }
}
const resp = await db.put(user)
return {
_rev: resp.rev,
@ -342,38 +373,9 @@ export default class TestConfiguration {
}
}
async createUser(
user: {
id?: string
firstName?: string
lastName?: string
email?: string
builder?: boolean
admin?: boolean
roles?: UserRoles
} = {}
): Promise<User> {
const {
id,
firstName = generator.first(),
lastName = generator.last(),
email = generator.email(),
builder = true,
admin,
roles,
} = user
const globalId = !id ? `us_${Math.random()}` : `us_${id}`
const resp = await this.globalUser({
id: globalId,
firstName,
lastName,
email,
builder,
admin,
roles: roles || {},
})
await cache.user.invalidateUser(globalId)
async createUser(user: Partial<User> = {}): Promise<User> {
const resp = await this.globalUser(user)
await cache.user.invalidateUser(resp._id!)
return resp
}
@ -381,7 +383,7 @@ export default class TestConfiguration {
return context.doInTenant(this.tenantId!, async () => {
const baseGroup = structures.userGroups.userGroup()
baseGroup.roles = {
[this.prodAppId]: roleId,
[this.getProdAppId()]: roleId,
}
const { id, rev } = await pro.sdk.groups.save(baseGroup)
return {
@ -404,8 +406,18 @@ export default class TestConfiguration {
})
}
async login({ roleId, userId, builder, prodApp = false }: any = {}) {
const appId = prodApp ? this.prodAppId : this.appId
async login({
roleId,
userId,
builder,
prodApp,
}: {
roleId?: string
userId: string
builder: boolean
prodApp: boolean
}) {
const appId = prodApp ? this.getProdAppId() : this.getAppId()
return context.doInAppContext(appId, async () => {
userId = !userId ? `us_uuid1` : userId
if (!this.request) {
@ -414,9 +426,9 @@ export default class TestConfiguration {
// make sure the user exists in the global DB
if (roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
await this.globalUser({
id: userId,
builder,
roles: { [this.prodAppId]: roleId },
_id: userId,
builder: { global: builder },
roles: { [appId]: roleId || roles.BUILTIN_ROLE_IDS.BASIC },
})
}
await sessions.createASession(userId, {
@ -445,8 +457,9 @@ export default class TestConfiguration {
defaultHeaders(extras = {}, prodApp = false) {
const tenantId = this.getTenantId()
const user = this.getUser()
const authObj: AuthToken = {
userId: this.globalUserId,
userId: user._id!,
sessionId: "sessionid",
tenantId,
}
@ -498,7 +511,7 @@ export default class TestConfiguration {
builder = false,
prodApp = true,
} = {}) {
return this.login({ email, roleId, builder, prodApp })
return this.login({ userId: email, roleId, builder, prodApp })
}
// TENANCY
@ -521,18 +534,22 @@ export default class TestConfiguration {
this.tenantId = structures.tenant.id()
this.user = await this.globalUser()
this.userMetadataId = generateUserMetadataID(this.user._id)
this.userMetadataId = generateUserMetadataID(this.user._id!)
return this.createApp(appName)
}
doInTenant(task: any) {
doInTenant<T>(task: () => T) {
return context.doInTenant(this.getTenantId(), task)
}
// API
async generateApiKey(userId = this.user._id) {
async generateApiKey(userId?: string) {
const user = this.getUser()
if (!userId) {
userId = user._id!
}
const db = tenancy.getTenantDB(this.getTenantId())
const id = dbCore.generateDevInfoID(userId)
let devInfo: any
@ -552,25 +569,28 @@ export default class TestConfiguration {
async createApp(appName: string): Promise<App> {
// create dev app
// clear any old app
this.appId = null
this.app = await context.doInTenant(this.tenantId!, async () => {
const app = await this._req({ name: appName }, null, appController.create)
this.appId = app.appId!
return app
})
return await context.doInAppContext(this.getAppId(), async () => {
this.appId = undefined
this.app = await context.doInTenant(
this.tenantId!,
async () =>
(await this._req(appController.create, {
name: appName,
})) as App
)
this.appId = this.app.appId
return await context.doInAppContext(this.app.appId!, async () => {
// create production app
this.prodApp = await this.publish()
this.allApps.push(this.prodApp)
this.allApps.push(this.app)
this.allApps.push(this.app!)
return this.app!
})
}
async publish() {
await this._req(null, null, deployController.publishApp)
await this._req(deployController.publishApp)
// @ts-ignore
const prodAppId = this.getAppId().replace("_dev", "")
this.prodAppId = prodAppId
@ -582,13 +602,11 @@ export default class TestConfiguration {
}
async unpublish() {
const response = await this._req(
null,
{ appId: this.appId },
appController.unpublish
)
this.prodAppId = null
this.prodApp = null
const response = await this._req(appController.unpublish, {
appId: this.appId,
})
this.prodAppId = undefined
this.prodApp = undefined
return response
}
@ -716,8 +734,7 @@ export default class TestConfiguration {
// ROLE
async createRole(config?: any) {
config = config || basicRole()
return this._req(config, null, roleController.save)
return this._req(roleController.save, config || basicRole())
}
// VIEW
@ -730,7 +747,7 @@ export default class TestConfiguration {
tableId: this.table!._id,
name: generator.guid(),
}
return this._req(view, null, viewController.v1.save)
return this._req(viewController.v1.save, view)
}
async createView(
@ -754,40 +771,38 @@ export default class TestConfiguration {
// AUTOMATION
async createAutomation(config?: any) {
async createAutomation(config?: Automation) {
config = config || basicAutomation()
if (config._rev) {
delete config._rev
}
this.automation = (
await this._req(config, null, automationController.create)
).automation
const res = await this._req(automationController.create, config)
this.automation = res.automation
return this.automation
}
async getAllAutomations() {
return this._req(null, null, automationController.fetch)
return this._req(automationController.fetch)
}
async deleteAutomation(automation?: any) {
async deleteAutomation(automation?: Automation) {
automation = automation || this.automation
if (!automation) {
return
}
return this._req(
null,
{ id: automation._id, rev: automation._rev },
automationController.destroy
)
return this._req(automationController.destroy, undefined, {
id: automation._id,
rev: automation._rev,
})
}
async createWebhook(config?: any) {
async createWebhook(config?: Webhook) {
if (!this.automation) {
throw "Must create an automation before creating webhook."
}
config = config || basicWebhook(this.automation._id)
config = config || basicWebhook(this.automation._id!)
return (await this._req(config, null, webhookController.save)).webhook
return (await this._req(webhookController.save, config)).webhook
}
// DATASOURCE
@ -809,7 +824,7 @@ export default class TestConfiguration {
return { ...this.datasource, _id: this.datasource!._id! }
}
async restDatasource(cfg?: any) {
async restDatasource(cfg?: Record<string, any>) {
return this.createDatasource({
datasource: {
...basicDatasource().datasource,
@ -866,26 +881,25 @@ export default class TestConfiguration {
// QUERY
async createQuery(config?: any) {
if (!this.datasource && !config) {
throw "No datasource created for query."
}
config = config || basicQuery(this.datasource!._id!)
return this._req(config, null, queryController.save)
async createQuery(config?: Query) {
return this._req(
queryController.save,
config || basicQuery(this.getDatasource()._id!)
)
}
// SCREEN
async createScreen(config?: any) {
async createScreen(config?: Screen) {
config = config || basicScreen()
return this._req(config, null, screenController.save)
return this._req(screenController.save, config)
}
// LAYOUT
async createLayout(config?: any) {
async createLayout(config?: Layout) {
config = config || basicLayout()
return await this._req(config, null, layoutController.save)
return await this._req(layoutController.save, config)
}
}

View File

@ -22,6 +22,8 @@ import {
INTERNAL_TABLE_SOURCE_ID,
TableSourceType,
Query,
Webhook,
WebhookActionType,
} from "@budibase/types"
import { LoopInput, LoopStepType } from "../../definitions/automations"
@ -407,12 +409,12 @@ export function basicLayout() {
return cloneDeep(EMPTY_LAYOUT)
}
export function basicWebhook(automationId: string) {
export function basicWebhook(automationId: string): Webhook {
return {
live: true,
name: "webhook",
action: {
type: "automation",
type: WebhookActionType.AUTOMATION,
target: automationId,
},
}

View File

@ -0,0 +1,3 @@
import { DocumentDestroyResponse } from "@budibase/nano"
export interface DeleteAutomationResponse extends DocumentDestroyResponse {}

View File

@ -11,3 +11,5 @@ export * from "./global"
export * from "./pagination"
export * from "./searchFilter"
export * from "./cookies"
export * from "./automation"
export * from "./layout"

View File

@ -0,0 +1,5 @@
import { Layout } from "../../documents"
export interface SaveLayoutRequest extends Layout {}
export interface SaveLayoutResponse extends Layout {}

View File

@ -1,6 +1,11 @@
import { Document } from "../document"
export interface Layout extends Document {
componentLibraries: string[]
title: string
favicon: string
stylesheets: string[]
props: any
layoutId?: string
name?: string
}

View File

@ -22,4 +22,5 @@ export interface Screen extends Document {
routing: ScreenRouting
props: ScreenProps
name?: string
pluginAdded?: boolean
}

View File

@ -280,7 +280,7 @@ class TestConfiguration {
const db = context.getGlobalDB()
const id = dbCore.generateDevInfoID(this.user!._id)
const id = dbCore.generateDevInfoID(this.user!._id!)
// TODO: dry
this.apiKey = encryption.encrypt(
`${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}`