Support path variable tenancy detection, add /api/system/* tests, update no tenancy matchers to be more accurate
This commit is contained in:
parent
6783fd713e
commit
72562278c0
|
@ -1,15 +1,16 @@
|
|||
const { getGlobalUserParams, getAllApps } = require("../db/utils")
|
||||
const { doWithDB } = require("../db")
|
||||
const { doWithGlobalDB } = require("../tenancy")
|
||||
const { StaticDatabases } = require("../db/constants")
|
||||
import { getGlobalUserParams, getAllApps } from "../db/utils"
|
||||
import { doWithDB } from "../db"
|
||||
import { doWithGlobalDB } from "../tenancy"
|
||||
import { StaticDatabases } from "../db/constants"
|
||||
import { App, Tenants, User } from "@budibase/types"
|
||||
|
||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||
|
||||
const removeTenantFromInfoDB = async tenantId => {
|
||||
const removeTenantFromInfoDB = async (tenantId: string) => {
|
||||
try {
|
||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
||||
let tenants = await infoDb.get(TENANT_DOC)
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
||||
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
||||
|
||||
await infoDb.put(tenants)
|
||||
|
@ -20,14 +21,14 @@ const removeTenantFromInfoDB = async tenantId => {
|
|||
}
|
||||
}
|
||||
|
||||
exports.removeUserFromInfoDB = async dbUser => {
|
||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
||||
export const removeUserFromInfoDB = async (dbUser: User) => {
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
const keys = [dbUser._id, dbUser.email]
|
||||
const userDocs = await infoDb.allDocs({
|
||||
keys,
|
||||
include_docs: true,
|
||||
})
|
||||
const toDelete = userDocs.rows.map(row => {
|
||||
const toDelete = userDocs.rows.map((row: any) => {
|
||||
return {
|
||||
...row.doc,
|
||||
_deleted: true,
|
||||
|
@ -37,18 +38,18 @@ exports.removeUserFromInfoDB = async dbUser => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeUsersFromInfoDB = async tenantId => {
|
||||
return doWithGlobalDB(tenantId, async db => {
|
||||
const removeUsersFromInfoDB = async (tenantId: string) => {
|
||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||
try {
|
||||
const allUsers = await db.allDocs(
|
||||
getGlobalUserParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
await doWithDB(PLATFORM_INFO_DB, async infoDb => {
|
||||
const allEmails = allUsers.rows.map(row => row.doc.email)
|
||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
||||
const allEmails = allUsers.rows.map((row: any) => row.doc.email)
|
||||
// get the id docs
|
||||
let keys = allUsers.rows.map(row => row.id)
|
||||
let keys = allUsers.rows.map((row: any) => row.id)
|
||||
// and the email docs
|
||||
keys = keys.concat(allEmails)
|
||||
// retrieve the docs and delete them
|
||||
|
@ -56,7 +57,7 @@ const removeUsersFromInfoDB = async tenantId => {
|
|||
keys,
|
||||
include_docs: true,
|
||||
})
|
||||
const toDelete = userDocs.rows.map(row => {
|
||||
const toDelete = userDocs.rows.map((row: any) => {
|
||||
return {
|
||||
...row.doc,
|
||||
_deleted: true,
|
||||
|
@ -71,8 +72,8 @@ const removeUsersFromInfoDB = async tenantId => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeGlobalDB = async tenantId => {
|
||||
return doWithGlobalDB(tenantId, async db => {
|
||||
const removeGlobalDB = async (tenantId: string) => {
|
||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||
try {
|
||||
await db.destroy()
|
||||
} catch (err) {
|
||||
|
@ -82,11 +83,11 @@ const removeGlobalDB = async tenantId => {
|
|||
})
|
||||
}
|
||||
|
||||
const removeTenantApps = async tenantId => {
|
||||
const removeTenantApps = async (tenantId: string) => {
|
||||
try {
|
||||
const apps = await getAllApps({ all: true })
|
||||
const apps = (await getAllApps({ all: true })) as App[]
|
||||
const destroyPromises = apps.map(app =>
|
||||
doWithDB(app.appId, db => db.destroy())
|
||||
doWithDB(app.appId, (db: any) => db.destroy())
|
||||
)
|
||||
await Promise.allSettled(destroyPromises)
|
||||
} catch (err) {
|
||||
|
@ -96,7 +97,7 @@ const removeTenantApps = async tenantId => {
|
|||
}
|
||||
|
||||
// can't live in tenancy package due to circular dependency on db/utils
|
||||
exports.deleteTenant = async tenantId => {
|
||||
export const deleteTenant = async (tenantId: string) => {
|
||||
await removeTenantFromInfoDB(tenantId)
|
||||
await removeUsersFromInfoDB(tenantId)
|
||||
await removeGlobalDB(tenantId)
|
|
@ -11,7 +11,7 @@ import env from "./environment"
|
|||
import tenancy from "./tenancy"
|
||||
import featureFlags from "./featureFlags"
|
||||
import * as sessions from "./security/sessions"
|
||||
import deprovisioning from "./context/deprovision"
|
||||
import * as deprovisioning from "./context/deprovision"
|
||||
import auth from "./auth"
|
||||
import constants from "./constants"
|
||||
import * as dbConstants from "./db/constants"
|
||||
|
|
|
@ -1,27 +1,35 @@
|
|||
import { BBContext, EndpointMatcher, RegexMatcher } from "@budibase/types"
|
||||
|
||||
const PARAM_REGEX = /\/:(.*?)(\/.*)?$/g
|
||||
|
||||
exports.buildMatcherRegex = patterns => {
|
||||
export const buildMatcherRegex = (
|
||||
patterns: EndpointMatcher[]
|
||||
): RegexMatcher[] => {
|
||||
if (!patterns) {
|
||||
return []
|
||||
}
|
||||
return patterns.map(pattern => {
|
||||
const isObj = typeof pattern === "object" && pattern.route
|
||||
const method = isObj ? pattern.method : "GET"
|
||||
let route = pattern.route
|
||||
const method = pattern.method
|
||||
const strict = pattern.strict ? pattern.strict : false
|
||||
let route = isObj ? pattern.route : pattern
|
||||
|
||||
// if there is a param in the route
|
||||
// use a wildcard pattern
|
||||
const matches = route.match(PARAM_REGEX)
|
||||
if (matches) {
|
||||
for (let match of matches) {
|
||||
const pattern = "/.*" + (match.endsWith("/") ? "/" : "")
|
||||
const suffix = match.endsWith("/") ? "/" : ""
|
||||
const pattern = "/.*" + suffix
|
||||
route = route.replace(match, pattern)
|
||||
console.log(route)
|
||||
}
|
||||
}
|
||||
|
||||
return { regex: new RegExp(route), method, strict, route }
|
||||
})
|
||||
}
|
||||
|
||||
exports.matches = (ctx, options) => {
|
||||
export const matches = (ctx: BBContext, options: RegexMatcher[]) => {
|
||||
return options.find(({ regex, method, strict, route }) => {
|
||||
let urlMatch
|
||||
if (strict) {
|
|
@ -1,5 +1,6 @@
|
|||
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
||||
import { buildMatcherRegex, matches } from "./matchers"
|
||||
import { Headers } from "../constants"
|
||||
import {
|
||||
BBContext,
|
||||
EndpointMatcher,
|
||||
|
@ -9,7 +10,7 @@ import {
|
|||
|
||||
const tenancy = (
|
||||
allowQueryStringPatterns: EndpointMatcher[],
|
||||
noTenancyPatterns: EndpointMatcher,
|
||||
noTenancyPatterns: EndpointMatcher[],
|
||||
opts = { noTenancyRequired: false }
|
||||
) => {
|
||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||
|
@ -28,6 +29,7 @@ const tenancy = (
|
|||
}
|
||||
|
||||
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
||||
ctx.set(Headers.TENANT_ID, tenantId as string)
|
||||
return doInTenant(tenantId, next)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,12 +214,31 @@ export const getTenantIDFromCtx = (
|
|||
// e.g. tenant.budibase.app or tenant.local.com
|
||||
const requestHost = ctx.host
|
||||
// parse the tenant id from the difference
|
||||
const tenantId = requestHost.substring(
|
||||
0,
|
||||
requestHost.indexOf(`.${platformHost}`)
|
||||
if (requestHost.includes(platformHost)) {
|
||||
const tenantId = requestHost.substring(
|
||||
0,
|
||||
requestHost.indexOf(`.${platformHost}`)
|
||||
)
|
||||
if (tenantId) {
|
||||
return tenantId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isAllowed(TenantResolutionStrategy.PATH)) {
|
||||
// params - have to parse manually due to koa-router not run yet
|
||||
const match = ctx.matched.find(
|
||||
(m: any) => !!m.paramNames.find((p: any) => p.name === "tenantId")
|
||||
)
|
||||
if (tenantId) {
|
||||
return tenantId
|
||||
if (match) {
|
||||
const params = match.params(
|
||||
ctx.originalUrl,
|
||||
match.captures(ctx.originalUrl),
|
||||
{}
|
||||
)
|
||||
if (params.tenantId) {
|
||||
return params.tenantId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,12 @@ import userCache from "./cache/user"
|
|||
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
||||
import * as events from "./events"
|
||||
import tenancy from "./tenancy"
|
||||
import { App, BBContext, TenantResolutionStrategy } from "@budibase/types"
|
||||
import {
|
||||
App,
|
||||
BBContext,
|
||||
PlatformLogoutOpts,
|
||||
TenantResolutionStrategy,
|
||||
} from "@budibase/types"
|
||||
import { SetOption } from "cookies"
|
||||
|
||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||
|
@ -189,12 +194,6 @@ export const getBuildersCount = async () => {
|
|||
return builders.length
|
||||
}
|
||||
|
||||
interface PlatformLogoutOpts {
|
||||
ctx: BBContext
|
||||
userId: string
|
||||
keepActiveSession: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||
*/
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * as mocks from "./mocks"
|
||||
export * as structures from "./structures"
|
||||
export { generator } from "./structures"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
export const getAccount = jest.fn()
|
||||
export const getAccountByTenantId = jest.fn()
|
||||
export const getStatus = jest.fn()
|
||||
|
||||
jest.mock("../../../src/cloud/accounts", () => ({
|
||||
getAccount,
|
||||
getAccountByTenantId,
|
||||
getStatus,
|
||||
}))
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { generator, uuid } from "."
|
||||
import { AuthType, CloudAccount, Hosting } from "@budibase/types"
|
||||
import * as db from "../../../src/db/utils"
|
||||
import { Account, AuthType, CloudAccount, Hosting } from "@budibase/types"
|
||||
|
||||
export const cloudAccount = (): CloudAccount => {
|
||||
export const account = (): Account => {
|
||||
return {
|
||||
accountId: uuid(),
|
||||
tenantId: generator.word(),
|
||||
email: generator.email(),
|
||||
tenantName: generator.word(),
|
||||
hosting: Hosting.SELF,
|
||||
createdAt: Date.now(),
|
||||
verified: true,
|
||||
verificationSent: true,
|
||||
tier: "",
|
||||
email: generator.email(),
|
||||
tenantId: generator.word(),
|
||||
hosting: Hosting.CLOUD,
|
||||
tier: "FREE", // DEPRECATED
|
||||
authType: AuthType.PASSWORD,
|
||||
password: generator.word(),
|
||||
tenantName: generator.word(),
|
||||
name: generator.name(),
|
||||
size: "10+",
|
||||
profession: "Software Engineer",
|
||||
}
|
||||
}
|
||||
|
||||
export const cloudAccount = (): CloudAccount => {
|
||||
return {
|
||||
...account(),
|
||||
hosting: Hosting.CLOUD,
|
||||
budibaseUserId: db.generateGlobalUserID(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./info"
|
||||
export * from "./users"
|
||||
export * from "./accounts"
|
||||
export * from "./tenants"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Document } from "../document"
|
||||
|
||||
export interface Tenants extends Document {
|
||||
tenantIds: string[]
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { BBContext } from "./koa"
|
||||
|
||||
export interface AuthToken {
|
||||
userId: string
|
||||
tenantId: string
|
||||
|
@ -25,3 +27,9 @@ export interface SessionKey {
|
|||
export interface ScannedSession {
|
||||
value: Session
|
||||
}
|
||||
|
||||
export interface PlatformLogoutOpts {
|
||||
ctx: BBContext
|
||||
userId: string
|
||||
keepActiveSession: boolean
|
||||
}
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
export interface EndpointMatcher {
|
||||
/**
|
||||
* The HTTP Path. e.g. /api/things/:thingId
|
||||
*/
|
||||
route: string
|
||||
/**
|
||||
* The HTTP Verb. e.g. GET, POST, etc.
|
||||
* ALL is also accepted to cover all verbs.
|
||||
*/
|
||||
method: string
|
||||
/**
|
||||
* The route must match exactly - not just begins with
|
||||
*/
|
||||
strict?: boolean
|
||||
}
|
||||
|
||||
export interface RegexMatcher {
|
||||
regex: RegExp
|
||||
method: string
|
||||
strict: boolean
|
||||
route: string
|
||||
}
|
||||
|
|
|
@ -9,4 +9,5 @@ export enum TenantResolutionStrategy {
|
|||
HEADER = "header",
|
||||
QUERY = "query",
|
||||
SUBDOMAIN = "subdomain",
|
||||
PATH = "path",
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
const env = require("../src/environment")
|
||||
|
||||
env._set("SELF_HOSTED", "1")
|
||||
env._set("SELF_HOSTED", "0")
|
||||
env._set("NODE_ENV", "jest")
|
||||
env._set("JWT_SECRET", "test-jwtsecret")
|
||||
env._set("LOG_LEVEL", "silent")
|
||||
env._set("MULTI_TENANCY", true)
|
||||
env._set("PLATFORM_URL", "http://localhost:10000")
|
||||
env._set("INTERNAL_API_KEY", "test")
|
||||
env._set("DISABLE_ACCOUNT_PORTAL", false)
|
||||
|
||||
const { mocks } = require("@budibase/backend-core/tests")
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const env = require("../../../environment")
|
||||
import { BBContext } from "@budibase/types"
|
||||
import env from "../../../environment"
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
export const fetch = async (ctx: BBContext) => {
|
||||
ctx.body = {
|
||||
multiTenancy: !!env.MULTI_TENANCY,
|
||||
cloud: !env.SELF_HOSTED,
|
|
@ -1,7 +1,8 @@
|
|||
const accounts = require("@budibase/backend-core/accounts")
|
||||
const env = require("../../../environment")
|
||||
import { accounts } from "@budibase/backend-core"
|
||||
import env from "../../../environment"
|
||||
import { BBContext } from "@budibase/types"
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
export const fetch = async (ctx: BBContext) => {
|
||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||
const status = await accounts.getStatus()
|
||||
ctx.body = status
|
|
@ -1,60 +1,17 @@
|
|||
const { StaticDatabases, doWithDB } = require("@budibase/backend-core/db")
|
||||
const { getTenantId } = require("@budibase/backend-core/tenancy")
|
||||
const { deleteTenant } = require("@budibase/backend-core/deprovision")
|
||||
import { BBContext } from "@budibase/types"
|
||||
import { deprovisioning } from "@budibase/backend-core"
|
||||
import { quotas } from "@budibase/pro"
|
||||
|
||||
export const exists = async (ctx: any) => {
|
||||
const tenantId = ctx.request.params
|
||||
ctx.body = {
|
||||
exists: await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: any) => {
|
||||
let exists = false
|
||||
try {
|
||||
const tenantsDoc = await db.get(
|
||||
StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
)
|
||||
if (tenantsDoc) {
|
||||
exists = tenantsDoc.tenantIds.indexOf(tenantId) !== -1
|
||||
}
|
||||
} catch (err) {
|
||||
// if error it doesn't exist
|
||||
}
|
||||
return exists
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
const _delete = async (ctx: BBContext) => {
|
||||
const user = ctx.user!
|
||||
const tenantId = ctx.params.tenantId
|
||||
|
||||
export const fetch = async (ctx: any) => {
|
||||
ctx.body = await doWithDB(
|
||||
StaticDatabases.PLATFORM_INFO.name,
|
||||
async (db: any) => {
|
||||
let tenants = []
|
||||
try {
|
||||
const tenantsDoc = await db.get(
|
||||
StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||
)
|
||||
if (tenantsDoc) {
|
||||
tenants = tenantsDoc.tenantIds
|
||||
}
|
||||
} catch (err) {
|
||||
// if error it doesn't exist
|
||||
}
|
||||
return tenants
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const _delete = async (ctx: any) => {
|
||||
const tenantId = getTenantId()
|
||||
|
||||
if (ctx.params.tenantId !== tenantId) {
|
||||
ctx.throw(403, "Unauthorized")
|
||||
if (tenantId !== user.tenantId) {
|
||||
ctx.throw(403, "Tenant ID does not match current user")
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteTenant(tenantId)
|
||||
await deprovisioning.deleteTenant(tenantId)
|
||||
await quotas.bustCache()
|
||||
ctx.status = 204
|
||||
} catch (err) {
|
||||
|
|
|
@ -7,11 +7,12 @@ import { errors, auth, middleware } from "@budibase/backend-core"
|
|||
import { APIError } from "@budibase/types"
|
||||
|
||||
const PUBLIC_ENDPOINTS = [
|
||||
// old deprecated endpoints kept for backwards compat
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/google/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/oidc/callback",
|
||||
method: "GET",
|
||||
|
@ -51,10 +52,12 @@ const PUBLIC_ENDPOINTS = [
|
|||
route: "api/system/status",
|
||||
method: "GET",
|
||||
},
|
||||
// TODO: This should be an internal api
|
||||
{
|
||||
route: "/api/global/users/tenant/:id",
|
||||
method: "GET",
|
||||
},
|
||||
// TODO: This should be an internal api
|
||||
{
|
||||
route: "/api/system/restored",
|
||||
method: "POST",
|
||||
|
@ -62,17 +65,37 @@ const PUBLIC_ENDPOINTS = [
|
|||
]
|
||||
|
||||
const NO_TENANCY_ENDPOINTS = [
|
||||
...PUBLIC_ENDPOINTS,
|
||||
// system endpoints are not specific to any tenant
|
||||
{
|
||||
route: "/api/system",
|
||||
method: "ALL",
|
||||
},
|
||||
// tenant is determined in request body
|
||||
// used for creating the tenant
|
||||
{
|
||||
route: "/api/global/users/self",
|
||||
route: "/api/global/users/init",
|
||||
method: "POST",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/admin/auth/google/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// deprecated single tenant sso callback
|
||||
{
|
||||
route: "/api/global/self",
|
||||
route: "/api/admin/auth/oidc/callback",
|
||||
method: "GET",
|
||||
},
|
||||
// tenant is determined from code in redis
|
||||
{
|
||||
route: "/api/global/users/invite/accept",
|
||||
method: "POST",
|
||||
},
|
||||
// global user search - no tenancy
|
||||
// :id is user id
|
||||
// TODO: this should really be `/api/system/users/:id`
|
||||
{
|
||||
route: "/api/global/users/tenant/:id",
|
||||
method: "GET",
|
||||
},
|
||||
]
|
||||
|
|
|
@ -2,7 +2,6 @@ const Router = require("@koa/router")
|
|||
const authController = require("../../controllers/global/auth")
|
||||
const { joiValidator } = require("@budibase/backend-core/auth")
|
||||
const Joi = require("joi")
|
||||
const { updateTenantId } = require("@budibase/backend-core/tenancy")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
|
@ -29,43 +28,28 @@ function buildResetUpdateValidation() {
|
|||
}).required().unknown(false))
|
||||
}
|
||||
|
||||
function updateTenant(ctx, next) {
|
||||
if (ctx.params) {
|
||||
updateTenantId(ctx.params.tenantId)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
router
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/login",
|
||||
buildAuthValidation(),
|
||||
updateTenant,
|
||||
authController.authenticate
|
||||
)
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/reset",
|
||||
buildResetValidation(),
|
||||
updateTenant,
|
||||
authController.reset
|
||||
)
|
||||
.post(
|
||||
"/api/global/auth/:tenantId/reset/update",
|
||||
buildResetUpdateValidation(),
|
||||
updateTenant,
|
||||
authController.resetUpdate
|
||||
)
|
||||
.post("/api/global/auth/logout", authController.logout)
|
||||
.post("/api/global/auth/init", authController.setInitInfo)
|
||||
.get("/api/global/auth/init", authController.getInitInfo)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/google",
|
||||
updateTenant,
|
||||
authController.googlePreAuth
|
||||
)
|
||||
.get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider",
|
||||
updateTenant,
|
||||
authController.datasourcePreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
|
@ -75,29 +59,19 @@ router
|
|||
authController.datasourceAuth
|
||||
)
|
||||
// multi-tenancy endpoint
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/google/callback",
|
||||
updateTenant,
|
||||
authController.googleAuth
|
||||
)
|
||||
.get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/datasource/:provider/callback",
|
||||
updateTenant,
|
||||
authController.datasourceAuth
|
||||
)
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/oidc/configs/:configId",
|
||||
updateTenant,
|
||||
authController.oidcPreAuth
|
||||
)
|
||||
// single tenancy endpoint
|
||||
.get("/api/global/auth/oidc/callback", authController.oidcAuth)
|
||||
// multi-tenancy endpoint
|
||||
.get(
|
||||
"/api/global/auth/:tenantId/oidc/callback",
|
||||
updateTenant,
|
||||
authController.oidcAuth
|
||||
)
|
||||
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
|
||||
// deprecated - used by the default system before tenancy
|
||||
.get("/api/admin/auth/google/callback", authController.googleAuth)
|
||||
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
|
||||
|
|
|
@ -235,7 +235,7 @@ describe("configs", () => {
|
|||
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.platformURLUpdated).toBeCalledTimes(1)
|
||||
config.modeAccount()
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -257,7 +257,7 @@ describe("configs", () => {
|
|||
expect(events.org.nameUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.logoUpdated).toBeCalledTimes(1)
|
||||
expect(events.org.platformURLUpdated).toBeCalledTimes(1)
|
||||
config.modeAccount()
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/global/license", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/global/license/activate", () => {})
|
||||
|
||||
describe("POST /api/global/license/refresh", () => {})
|
||||
|
||||
describe("GET /api/global/license/info", () => {})
|
||||
|
||||
describe("DELETE /api/global/license/info", () => {})
|
||||
|
||||
describe("GET /api/global/license/usage", () => {})
|
||||
})
|
|
@ -0,0 +1,24 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/global/roles", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/roles", () => {})
|
||||
|
||||
describe("GET /api/global/roles/:appId", () => {})
|
||||
|
||||
describe("DELETE /api/global/roles/:appId", () => {})
|
||||
})
|
|
@ -0,0 +1,32 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/global/template", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/template/definitions", () => {})
|
||||
|
||||
describe("POST /api/global/template", () => {})
|
||||
|
||||
describe("GET /api/global/template", () => {})
|
||||
|
||||
describe("GET /api/global/template/:type", () => {})
|
||||
|
||||
describe("GET /api/global/template/:ownerId", () => {})
|
||||
|
||||
describe("GET /api/global/template/:id", () => {})
|
||||
|
||||
describe("DELETE /api/global/template/:id/:rev", () => {})
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/global/workspaces", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/global/workspaces", () => {})
|
||||
|
||||
describe("DELETE /api/global/workspaces/:id", () => {})
|
||||
|
||||
describe("GET /api/global/workspaces", () => {})
|
||||
|
||||
describe("GET /api/global/workspaces/:id", () => {})
|
||||
})
|
|
@ -1,8 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/environment")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/environment", controller.fetch)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,8 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/environment"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/environment", controller.fetch)
|
||||
|
||||
export default router
|
|
@ -1,8 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/status")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/status", controller.fetch)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,8 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/status"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get("/api/system/status", controller.fetch)
|
||||
|
||||
export default router
|
|
@ -1,12 +0,0 @@
|
|||
const Router = require("@koa/router")
|
||||
const controller = require("../../controllers/system/tenants")
|
||||
const { adminOnly } = require("@budibase/backend-core/auth")
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router
|
||||
.get("/api/system/tenants/:tenantId/exists", controller.exists)
|
||||
.get("/api/system/tenants", adminOnly, controller.fetch)
|
||||
.delete("/api/system/tenants/:tenantId", adminOnly, controller.delete)
|
||||
|
||||
module.exports = router
|
|
@ -0,0 +1,13 @@
|
|||
import Router from "@koa/router"
|
||||
import * as controller from "../../controllers/system/tenants"
|
||||
import { middleware } from "@budibase/backend-core"
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.delete(
|
||||
"/api/system/tenants/:tenantId",
|
||||
middleware.adminOnly,
|
||||
controller.delete
|
||||
)
|
||||
|
||||
export default router
|
|
@ -0,0 +1,30 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/system/environment", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/system/environment", () => {
|
||||
it("returns the expected environment", async () => {
|
||||
const env = await api.environment.getEnvironment()
|
||||
expect(env.body).toEqual({
|
||||
cloud: true,
|
||||
disableAccountPortal: false,
|
||||
isDev: false,
|
||||
multiTenancy: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,64 @@
|
|||
const migrateFn = jest.fn()
|
||||
|
||||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
jest.mock("../../../../migrations", () => {
|
||||
return {
|
||||
...jest.requireActual("../../../../migrations"),
|
||||
migrate: migrateFn,
|
||||
}
|
||||
})
|
||||
|
||||
describe("/api/system/migrations", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/system/migrations/run", () => {
|
||||
it("fails with no internal api key", async () => {
|
||||
const res = await api.migrations.runMigrations({
|
||||
headers: {},
|
||||
status: 403,
|
||||
})
|
||||
expect(res.text).toBe("Unauthorized - no public worker access")
|
||||
expect(migrateFn).toBeCalledTimes(0)
|
||||
})
|
||||
|
||||
it("runs migrations", async () => {
|
||||
const res = await api.migrations.runMigrations()
|
||||
expect(res.text).toBe("OK")
|
||||
expect(migrateFn).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /api/system/migrations/definitions", () => {
|
||||
it("fails with no internal api key", async () => {
|
||||
const res = await api.migrations.getMigrationDefinitions({
|
||||
headers: {},
|
||||
status: 403,
|
||||
})
|
||||
expect(res.text).toBe("Unauthorized - no public worker access")
|
||||
})
|
||||
|
||||
it("returns definitions", async () => {
|
||||
const res = await api.migrations.getMigrationDefinitions()
|
||||
expect(res.body).toEqual([
|
||||
{
|
||||
name: "global_info_sync_users",
|
||||
type: "global",
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,37 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/system/restore", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("POST /api/global/restore", () => {
|
||||
it("doesn't allow restore in cloud", async () => {
|
||||
const res = await api.restore.restored({ status: 405 })
|
||||
expect(res.body).toEqual({
|
||||
message: "This operation is not allowed in cloud.",
|
||||
status: 405,
|
||||
})
|
||||
})
|
||||
|
||||
it("restores in self host", async () => {
|
||||
config.modeSelf()
|
||||
const res = await api.restore.restored()
|
||||
expect(res.body).toEqual({
|
||||
message: "System prepared after restore.",
|
||||
})
|
||||
config.modeCloud()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,49 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
import { accounts } from "@budibase/backend-core"
|
||||
import { mocks } from "@budibase/backend-core/tests"
|
||||
|
||||
describe("/api/system/status", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("GET /api/system/status", () => {
|
||||
it("returns status in self host", async () => {
|
||||
config.modeSelf()
|
||||
const res = await api.status.getStatus()
|
||||
expect(res.body).toEqual({
|
||||
health: {
|
||||
passing: true,
|
||||
},
|
||||
})
|
||||
expect(accounts.getStatus).toBeCalledTimes(0)
|
||||
config.modeCloud()
|
||||
})
|
||||
|
||||
it("returns status in cloud", async () => {
|
||||
const value = {
|
||||
health: {
|
||||
passing: false,
|
||||
},
|
||||
}
|
||||
|
||||
mocks.accounts.getStatus.mockReturnValueOnce(value)
|
||||
|
||||
const res = await api.status.getStatus()
|
||||
|
||||
expect(accounts.getStatus).toBeCalledTimes(1)
|
||||
expect(res.body).toEqual(value)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,32 @@
|
|||
import { TestConfiguration, API } from "../../../../tests"
|
||||
|
||||
describe("/api/global/workspaces", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("DELETE /api/system/tenants/:tenantId", () => {
|
||||
it("allows deleting the current tenant", async () => {
|
||||
const user = await config.createTenant()
|
||||
await config.createSession(user)
|
||||
const res = await api.tenants.delete(user.tenantId, {
|
||||
headers: config.authHeaders(user),
|
||||
})
|
||||
})
|
||||
|
||||
it("rejects deleting another tenant", () => {})
|
||||
|
||||
it("requires admin", () => {})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,74 @@
|
|||
import { TestConfiguration, API, structures } from "../../tests"
|
||||
import { constants } from "@budibase/backend-core"
|
||||
|
||||
describe("tenancy middleware", () => {
|
||||
const config = new TestConfiguration()
|
||||
const api = new API(config)
|
||||
|
||||
beforeAll(async () => {
|
||||
await config.beforeAll()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await config.afterAll()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should get tenant id from user", async () => {
|
||||
const user = await config.createTenant()
|
||||
await config.createSession(user)
|
||||
const res = await api.self.getSelf(user)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(user.tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from header", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const headers = {
|
||||
[constants.Headers.TENANT_ID]: tenantId,
|
||||
}
|
||||
const res = await config.request
|
||||
.get(`/api/global/configs/checklist`)
|
||||
.set(headers)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from query param", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const res = await config.request.get(
|
||||
`/api/global/configs/checklist?tenantId=${tenantId}`
|
||||
)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from subdomain", async () => {
|
||||
const tenantId = structures.uuid()
|
||||
const headers = {
|
||||
host: `${tenantId}.localhost:10000`,
|
||||
}
|
||||
const res = await config.request
|
||||
.get(`/api/global/configs/checklist`)
|
||||
.set(headers)
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(tenantId)
|
||||
})
|
||||
|
||||
it("should get tenant id from path variable", async () => {
|
||||
const user = await config.createTenant()
|
||||
const res = await config.request
|
||||
.post(`/api/global/auth/${user.tenantId}/login`)
|
||||
.send({
|
||||
username: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(user.tenantId)
|
||||
})
|
||||
|
||||
it("should throw when no tenant id is found", async () => {
|
||||
const res = await config.request.get(`/api/global/configs/checklist`)
|
||||
expect(res.status).toBe(403)
|
||||
expect(res.text).toBe("Tenant id not set")
|
||||
expect(res.headers[constants.Headers.TENANT_ID]).toBe(undefined)
|
||||
})
|
||||
})
|
|
@ -2,6 +2,7 @@ import "./mocks"
|
|||
import dbConfig from "../db"
|
||||
dbConfig.init()
|
||||
import env from "../environment"
|
||||
import { env as coreEnv } from "@budibase/backend-core"
|
||||
import controllers from "./controllers"
|
||||
const supertest = require("supertest")
|
||||
import { Configs } from "../constants"
|
||||
|
@ -12,13 +13,13 @@ import {
|
|||
Headers,
|
||||
sessions,
|
||||
auth,
|
||||
constants,
|
||||
} from "@budibase/backend-core"
|
||||
import { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
||||
import structures from "./structures"
|
||||
import structures, { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
|
||||
import { CreateUserResponse, User, AuthToken } from "@budibase/types"
|
||||
|
||||
enum Mode {
|
||||
ACCOUNT = "account",
|
||||
CLOUD = "cloud",
|
||||
SELF = "self",
|
||||
}
|
||||
|
||||
|
@ -31,11 +32,11 @@ class TestConfiguration {
|
|||
constructor(
|
||||
opts: { openServer: boolean; mode: Mode } = {
|
||||
openServer: true,
|
||||
mode: Mode.ACCOUNT,
|
||||
mode: Mode.CLOUD,
|
||||
}
|
||||
) {
|
||||
if (opts.mode === Mode.ACCOUNT) {
|
||||
this.modeAccount()
|
||||
if (opts.mode === Mode.CLOUD) {
|
||||
this.modeCloud()
|
||||
} else if (opts.mode === Mode.SELF) {
|
||||
this.modeSelf()
|
||||
}
|
||||
|
@ -54,20 +55,24 @@ class TestConfiguration {
|
|||
|
||||
// MODES
|
||||
|
||||
modeAccount = () => {
|
||||
env.SELF_HOSTED = false
|
||||
// @ts-ignore
|
||||
env.MULTI_TENANCY = true
|
||||
// @ts-ignore
|
||||
env.DISABLE_ACCOUNT_PORTAL = false
|
||||
setMultiTenancy = (value: boolean) => {
|
||||
env._set("MULTI_TENANCY", value)
|
||||
coreEnv._set("MULTI_TENANCY", value)
|
||||
}
|
||||
|
||||
setSelfHosted = (value: boolean) => {
|
||||
env._set("SELF_HOSTED", value)
|
||||
coreEnv._set("SELF_HOSTED", value)
|
||||
}
|
||||
|
||||
modeCloud = () => {
|
||||
this.setSelfHosted(false)
|
||||
this.setMultiTenancy(true)
|
||||
}
|
||||
|
||||
modeSelf = () => {
|
||||
env.SELF_HOSTED = true
|
||||
// @ts-ignore
|
||||
env.MULTI_TENANCY = false
|
||||
// @ts-ignore
|
||||
env.DISABLE_ACCOUNT_PORTAL = true
|
||||
this.setSelfHosted(true)
|
||||
this.setMultiTenancy(false)
|
||||
}
|
||||
|
||||
// UTILS
|
||||
|
@ -114,6 +119,16 @@ class TestConfiguration {
|
|||
|
||||
// TENANCY
|
||||
|
||||
createTenant = async (tenantId?: string): Promise<User> => {
|
||||
// create user / new tenant
|
||||
if (!tenantId) {
|
||||
tenantId = structures.uuid()
|
||||
}
|
||||
return tenancy.doInTenant(tenantId, async () => {
|
||||
return this.createUser()
|
||||
})
|
||||
}
|
||||
|
||||
getTenantId() {
|
||||
try {
|
||||
return tenancy.getTenantId()
|
||||
|
@ -179,6 +194,10 @@ class TestConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
internalAPIHeaders() {
|
||||
return { [constants.Headers.API_KEY]: env.INTERNAL_API_KEY }
|
||||
}
|
||||
|
||||
async getUser(email: string): Promise<User> {
|
||||
return tenancy.doInTenant(this.getTenantId(), () => {
|
||||
return users.getGlobalUserByEmail(email)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
|
||||
export interface TestAPIOpts {
|
||||
headers?: any
|
||||
status?: number
|
||||
}
|
||||
|
||||
export abstract class TestAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
protected constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
|
||||
export class EnvironmentAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
}
|
||||
|
||||
getEnvironment = () => {
|
||||
return this.request
|
||||
.get(`/api/system/environment`)
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
}
|
|
@ -5,6 +5,11 @@ import { ConfigAPI } from "./configs"
|
|||
import { EmailAPI } from "./email"
|
||||
import { SelfAPI } from "./self"
|
||||
import { UserAPI } from "./users"
|
||||
import { EnvironmentAPI } from "./environment"
|
||||
import { MigrationAPI } from "./migrations"
|
||||
import { StatusAPI } from "./status"
|
||||
import { RestoreAPI } from "./restore"
|
||||
import { TenantAPI } from "./tenants"
|
||||
|
||||
export default class API {
|
||||
accounts: AccountAPI
|
||||
|
@ -13,6 +18,11 @@ export default class API {
|
|||
emails: EmailAPI
|
||||
self: SelfAPI
|
||||
users: UserAPI
|
||||
environment: EnvironmentAPI
|
||||
migrations: MigrationAPI
|
||||
status: StatusAPI
|
||||
restore: RestoreAPI
|
||||
tenants: TenantAPI
|
||||
|
||||
constructor(config: TestConfiguration) {
|
||||
this.accounts = new AccountAPI(config)
|
||||
|
@ -21,5 +31,10 @@ export default class API {
|
|||
this.emails = new EmailAPI(config)
|
||||
this.self = new SelfAPI(config)
|
||||
this.users = new UserAPI(config)
|
||||
this.environment = new EnvironmentAPI(config)
|
||||
this.migrations = new MigrationAPI(config)
|
||||
this.status = new StatusAPI(config)
|
||||
this.restore = new RestoreAPI(config)
|
||||
this.tenants = new TenantAPI(config)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class MigrationAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
runMigrations = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.post(`/api/system/migrations/run`)
|
||||
.set(opts?.headers ? opts.headers : this.config.internalAPIHeaders())
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
|
||||
getMigrationDefinitions = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.get(`/api/system/migrations/definitions`)
|
||||
.set(opts?.headers ? opts.headers : this.config.internalAPIHeaders())
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class RestoreAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
restored = (opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.post(`/api/system/restored`)
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
|
@ -18,4 +18,12 @@ export class SelfAPI {
|
|||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
|
||||
getSelf = (user: User) => {
|
||||
return this.request
|
||||
.get(`/api/global/self`)
|
||||
.set(this.config.authHeaders(user))
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
|
||||
export class StatusAPI {
|
||||
config: TestConfiguration
|
||||
request: any
|
||||
|
||||
constructor(config: TestConfiguration) {
|
||||
this.config = config
|
||||
this.request = config.request
|
||||
}
|
||||
|
||||
getStatus = () => {
|
||||
return this.request.get(`/api/system/status`).expect(200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import TestConfiguration from "../TestConfiguration"
|
||||
import { TestAPI, TestAPIOpts } from "./base"
|
||||
|
||||
export class TenantAPI extends TestAPI {
|
||||
constructor(config: TestConfiguration) {
|
||||
super(config)
|
||||
}
|
||||
|
||||
delete = (tenantId: string, opts?: TestAPIOpts) => {
|
||||
return this.request
|
||||
.delete(`/api/system/tenants/${tenantId}`)
|
||||
.set(opts?.headers)
|
||||
.expect(opts?.status ? opts.status : 200)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,14 @@
|
|||
import { generator } from "@budibase/backend-core/tests"
|
||||
import TestConfiguration from "./TestConfiguration"
|
||||
import structures from "./structures"
|
||||
import mocks from "./mocks"
|
||||
import API from "./api"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
const pkg = {
|
||||
structures,
|
||||
generator,
|
||||
uuid,
|
||||
TENANT_1: structures.TENANT_1,
|
||||
mocks,
|
||||
TestConfiguration,
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { Account, AuthType, Hosting, CloudAccount } from "@budibase/types"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { utils } from "@budibase/backend-core"
|
||||
|
||||
export const account = (): Account => {
|
||||
return {
|
||||
email: `${uuid()}@test.com`,
|
||||
tenantId: utils.newid(),
|
||||
hosting: Hosting.SELF,
|
||||
authType: AuthType.SSO,
|
||||
accountId: uuid(),
|
||||
createdAt: Date.now(),
|
||||
verified: true,
|
||||
verificationSent: true,
|
||||
tier: "FREE",
|
||||
}
|
||||
}
|
||||
|
||||
export const cloudAccount = (): CloudAccount => {
|
||||
return {
|
||||
...account(),
|
||||
budibaseUserId: uuid(),
|
||||
}
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
import { structures } from "@budibase/backend-core/tests"
|
||||
import configs from "./configs"
|
||||
import * as users from "./users"
|
||||
import * as groups from "./groups"
|
||||
import * as accounts from "./accounts"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
const TENANT_ID = "default"
|
||||
const TENANT_1 = "tenant1"
|
||||
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
|
||||
|
||||
const pkg = {
|
||||
...structures,
|
||||
uuid,
|
||||
configs,
|
||||
users,
|
||||
accounts,
|
||||
TENANT_ID,
|
||||
TENANT_1,
|
||||
CSRF_TOKEN,
|
||||
|
|
|
@ -5,6 +5,7 @@ import { v4 as uuid } from "uuid"
|
|||
export const newEmail = () => {
|
||||
return `${uuid()}@test.com`
|
||||
}
|
||||
|
||||
export const user = (userProps?: any): User => {
|
||||
return {
|
||||
email: newEmail(),
|
||||
|
|
Loading…
Reference in New Issue