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:
commit
af44a973b4
|
@ -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}`
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}`,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"`
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -29,6 +29,6 @@ start().catch(err => {
|
|||
throw err
|
||||
})
|
||||
|
||||
export function getServer() {
|
||||
export function getServer(): Server {
|
||||
return server
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import { DocumentDestroyResponse } from "@budibase/nano"
|
||||
|
||||
export interface DeleteAutomationResponse extends DocumentDestroyResponse {}
|
|
@ -11,3 +11,5 @@ export * from "./global"
|
|||
export * from "./pagination"
|
||||
export * from "./searchFilter"
|
||||
export * from "./cookies"
|
||||
export * from "./automation"
|
||||
export * from "./layout"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Layout } from "../../documents"
|
||||
|
||||
export interface SaveLayoutRequest extends Layout {}
|
||||
|
||||
export interface SaveLayoutResponse extends Layout {}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -22,4 +22,5 @@ export interface Screen extends Document {
|
|||
routing: ScreenRouting
|
||||
props: ScreenProps
|
||||
name?: string
|
||||
pluginAdded?: boolean
|
||||
}
|
||||
|
|
|
@ -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()}`
|
||||
|
|
Loading…
Reference in New Issue