1
0
Fork 0
mirror of synced 2024-10-04 03:54:37 +13:00

Merge pull request #10272 from Budibase/test/coverage-bckend-core

Test/coverage bckend core
This commit is contained in:
Michael Drury 2023-04-12 13:32:04 +01:00 committed by GitHub
commit 0911cc05cf
66 changed files with 1154 additions and 874 deletions

View file

@ -61,6 +61,7 @@
"@swc/core": "^1.3.25",
"@swc/jest": "^0.2.24",
"@trendyol/jest-testcontainers": "^2.1.1",
"@jest/test-sequencer": "29.5.0",
"@types/chance": "1.1.3",
"@types/ioredis": "4.28.0",
"@types/jest": "28.1.1",
@ -75,14 +76,14 @@
"@types/uuid": "8.3.4",
"chance": "1.1.8",
"ioredis-mock": "5.8.0",
"jest": "28.1.1",
"jest": "29.5.0",
"jest-serial-runner": "^1.2.1",
"koa": "2.13.4",
"nodemon": "2.0.16",
"pino-pretty": "10.0.0",
"pouchdb-adapter-memory": "7.2.2",
"timekeeper": "2.2.0",
"ts-jest": "28.0.4",
"ts-jest": "29.0.5",
"ts-node": "10.8.1",
"tsconfig-paths": "4.0.0",
"typescript": "4.7.3"

View file

@ -1,4 +1,5 @@
import { structures, testEnv } from "../../../tests"
import { structures } from "../../../tests"
import { testEnv } from "../../../tests/extra"
import * as auth from "../auth"
import * as events from "../../events"

View file

@ -1,6 +1,6 @@
import { DBTestConfiguration } from "../../../tests/extra"
import {
structures,
DBTestConfiguration,
expectFunctionWasCalledTimesWith,
mocks,
} from "../../../tests"

View file

@ -1,9 +1,5 @@
import {
DBTestConfiguration,
generator,
testEnv,
structures,
} from "../../../tests"
import { generator, structures } from "../../../tests"
import { DBTestConfiguration, testEnv } from "../../../tests/extra"
import { ConfigType } from "@budibase/types"
import env from "../../environment"
import * as configs from "../configs"

View file

@ -2,7 +2,7 @@
// store an app ID to pretend there is a context
import env from "../environment"
import Context from "./Context"
import * as conversions from "../db/conversions"
import * as conversions from "../docIds/conversions"
import { getDB } from "../db/db"
import {
DocumentType,
@ -43,8 +43,12 @@ export function baseGlobalDBName(tenantId: string | undefined | null) {
}
}
export function getPlatformURL() {
return env.PLATFORM_URL
}
export function isMultiTenant() {
return env.MULTI_TENANCY
return !!env.MULTI_TENANCY
}
export function isTenantIdSet() {

View file

@ -1,4 +1,4 @@
import { testEnv } from "../../../tests"
import { testEnv } from "../../../tests/extra"
import * as context from "../"
import { DEFAULT_TENANT_ID } from "../../constants"

View file

@ -15,7 +15,7 @@ import { getCouchInfo } from "./connections"
import { directCouchCall } from "./utils"
import { getPouchDB } from "./pouchDB"
import { WriteStream, ReadStream } from "fs"
import { newid } from "../../newid"
import { newid } from "../../docIds/newid"
function buildNano(couchInfo: { url: string; cookie: string }) {
return Nano({

View file

@ -2,7 +2,7 @@ export * from "./couch"
export * from "./db"
export * from "./utils"
export * from "./views"
export * from "./conversions"
export * from "../docIds/conversions"
export { default as Replication } from "./Replication"
// exports to support old export structure
export * from "../constants/db"

View file

@ -1,4 +1,4 @@
import { newid } from "../../newid"
import { newid } from "../../docIds/newid"
import { getDB } from "../db"
import { Database } from "@budibase/types"
import { QueryBuilder, paginatedSearch, fullSearch } from "../lucene"

View file

@ -3,7 +3,7 @@ import {
getProdAppID,
isDevAppID,
isProdAppID,
} from "../conversions"
} from "../../docIds/conversions"
import { generateAppID } from "../utils"
describe("utils", () => {

View file

@ -1,257 +1,12 @@
import { newid } from "../newid"
import env from "../environment"
import {
DEFAULT_TENANT_ID,
SEPARATOR,
DocumentType,
UNICODE_MAX,
ViewName,
InternalTable,
APP_PREFIX,
} from "../constants"
import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants"
import { getTenantId, getGlobalDBName } from "../context"
import { doWithDB, directCouchAllDbs } from "./db"
import { getAppMetadata } from "../cache/appMetadata"
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
import { App, Database } from "@budibase/types"
/**
* Generates a new app ID.
* @returns {string} The new app ID which the app doc can be stored under.
*/
export const generateAppID = (tenantId?: string | null) => {
let id = APP_PREFIX
if (tenantId) {
id += `${tenantId}${SEPARATOR}`
}
return `${id}${newid()}`
}
/**
* If creating DB allDocs/query params with only a single top level ID this can be used, this
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
* More complex cases such as link docs and rows which have multiple levels of IDs that their
* ID consists of need their own functions to build the allDocs parameters.
* @param {string} docType The type of document which input params are being built for, e.g. user,
* link, app, table and so on.
* @param {string|null} docId The ID of the document minus its type - this is only needed if looking
* for a singular document.
* @param {object} otherProps Add any other properties onto the request, e.g. include_docs.
* @returns {object} Parameters which can then be used with an allDocs request.
*/
export function getDocParams(
docType: string,
docId?: string | null,
otherProps: any = {}
) {
if (docId == null) {
docId = ""
}
return {
...otherProps,
startkey: `${docType}${SEPARATOR}${docId}`,
endkey: `${docType}${SEPARATOR}${docId}${UNICODE_MAX}`,
}
}
/**
* Gets the DB allDocs/query params for retrieving a row.
* @param {string|null} tableId The table in which the rows have been stored.
* @param {string|null} rowId The ID of the row which is being specifically queried for. This can be
* left null to get all the rows in the table.
* @param {object} otherProps Any other properties to add to the request.
* @returns {object} Parameters which can then be used with an allDocs request.
*/
export function getRowParams(
tableId?: string | null,
rowId?: string | null,
otherProps = {}
) {
if (tableId == null) {
return getDocParams(DocumentType.ROW, null, otherProps)
}
const endOfKey = rowId == null ? `${tableId}${SEPARATOR}` : rowId
return getDocParams(DocumentType.ROW, endOfKey, otherProps)
}
/**
* Retrieve the correct index for a view based on default design DB.
*/
export function getQueryIndex(viewName: ViewName) {
return `database/${viewName}`
}
/**
* Gets a new row ID for the specified table.
* @param {string} tableId The table which the row is being created for.
* @param {string|null} id If an ID is to be used then the UUID can be substituted for this.
* @returns {string} The new ID which a row doc can be stored under.
*/
export function generateRowID(tableId: string, id?: string) {
id = id || newid()
return `${DocumentType.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}`
}
/**
* Check if a given ID is that of a table.
* @returns {boolean}
*/
export const isTableId = (id: string) => {
// this includes datasource plus tables
return (
id &&
(id.startsWith(`${DocumentType.TABLE}${SEPARATOR}`) ||
id.startsWith(`${DocumentType.DATASOURCE_PLUS}${SEPARATOR}`))
)
}
/**
* Check if a given ID is that of a datasource or datasource plus.
* @returns {boolean}
*/
export const isDatasourceId = (id: string) => {
// this covers both datasources and datasource plus
return id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
}
/**
* Generates a new workspace ID.
* @returns {string} The new workspace ID which the workspace doc can be stored under.
*/
export function generateWorkspaceID() {
return `${DocumentType.WORKSPACE}${SEPARATOR}${newid()}`
}
/**
* Gets parameters for retrieving workspaces.
*/
export function getWorkspaceParams(id = "", otherProps = {}) {
return {
...otherProps,
startkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}`,
endkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`,
}
}
/**
* Generates a new global user ID.
* @returns {string} The new user ID which the user doc can be stored under.
*/
export function generateGlobalUserID(id?: any) {
return `${DocumentType.USER}${SEPARATOR}${id || newid()}`
}
/**
* Gets parameters for retrieving users.
*/
export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
if (!globalId) {
globalId = ""
}
const startkey = otherProps?.startkey
return {
...otherProps,
// need to include this incase pagination
startkey: startkey
? startkey
: `${DocumentType.USER}${SEPARATOR}${globalId}`,
endkey: `${DocumentType.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
}
}
/**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/
export function getUserMetadataParams(userId?: string | null, otherProps = {}) {
return getRowParams(InternalTable.USER_METADATA, userId, otherProps)
}
/**
* Generates a new user ID based on the passed in global ID.
* @param {string} globalId The ID of the global user.
* @returns {string} The new user ID which the user doc can be stored under.
*/
export function generateUserMetadataID(globalId: string) {
return generateRowID(InternalTable.USER_METADATA, globalId)
}
/**
* Breaks up the ID to get the global ID.
*/
export function getGlobalIDFromUserMetadataID(id: string) {
const prefix = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
if (!id || !id.includes(prefix)) {
return id
}
return id.split(prefix)[1]
}
export function getUsersByAppParams(appId: any, otherProps: any = {}) {
const prodAppId = getProdAppID(appId)
return {
...otherProps,
startkey: prodAppId,
endkey: `${prodAppId}${UNICODE_MAX}`,
}
}
/**
* 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) {
return `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
}
export function generateAppUserID(prodAppId: string, userId: string) {
return `${prodAppId}${SEPARATOR}${userId}`
}
/**
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level.
*/
export function getTemplateParams(
ownerId: any,
templateId: any,
otherProps = {}
) {
if (!templateId) {
templateId = ""
}
let final
if (templateId) {
final = templateId
} else {
final = `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}`
}
return {
...otherProps,
startkey: final,
endkey: `${final}${UNICODE_MAX}`,
}
}
/**
* Generates a new role ID.
* @returns {string} The new role ID which the role doc can be stored under.
*/
export function generateRoleID(id?: any) {
return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
}
/**
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
*/
export function getRoleParams(roleId?: string | null, otherProps = {}) {
return getDocParams(DocumentType.ROLE, roleId, otherProps)
}
export function getStartEndKeyURL(baseKey: any, tenantId?: string) {
const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : ""
return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"`
}
import { getStartEndKeyURL } from "../docIds"
export * from "../docIds"
/**
* if in production this will use the CouchDB _all_dbs call to retrieve a list of databases. If testing
@ -411,29 +166,6 @@ export async function dbExists(dbName: any) {
)
}
/**
* Generates a new dev info document ID - this is scoped to a user.
* @returns {string} The new dev info ID which info for dev (like api key) can be stored under.
*/
export const generateDevInfoID = (userId: any) => {
return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
}
/**
* Generates a new plugin ID - to be used in the global DB.
* @returns {string} The new plugin ID which a plugin metadata document can be stored under.
*/
export const generatePluginID = (name: string) => {
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
}
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
export const getPluginParams = (pluginId?: string | null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}
export function pagination<T>(
data: T[],
pageSize: number,

View file

@ -0,0 +1,102 @@
import {
APP_PREFIX,
DocumentType,
InternalTable,
SEPARATOR,
} from "../constants"
import { newid } from "./newid"
/**
* Generates a new app ID.
* @returns {string} The new app ID which the app doc can be stored under.
*/
export const generateAppID = (tenantId?: string | null) => {
let id = APP_PREFIX
if (tenantId) {
id += `${tenantId}${SEPARATOR}`
}
return `${id}${newid()}`
}
/**
* Gets a new row ID for the specified table.
* @param {string} tableId The table which the row is being created for.
* @param {string|null} id If an ID is to be used then the UUID can be substituted for this.
* @returns {string} The new ID which a row doc can be stored under.
*/
export function generateRowID(tableId: string, id?: string) {
id = id || newid()
return `${DocumentType.ROW}${SEPARATOR}${tableId}${SEPARATOR}${id}`
}
/**
* Generates a new workspace ID.
* @returns {string} The new workspace ID which the workspace doc can be stored under.
*/
export function generateWorkspaceID() {
return `${DocumentType.WORKSPACE}${SEPARATOR}${newid()}`
}
/**
* Generates a new global user ID.
* @returns {string} The new user ID which the user doc can be stored under.
*/
export function generateGlobalUserID(id?: any) {
return `${DocumentType.USER}${SEPARATOR}${id || newid()}`
}
/**
* Generates a new user ID based on the passed in global ID.
* @param {string} globalId The ID of the global user.
* @returns {string} The new user ID which the user doc can be stored under.
*/
export function generateUserMetadataID(globalId: string) {
return generateRowID(InternalTable.USER_METADATA, globalId)
}
/**
* Breaks up the ID to get the global ID.
*/
export function getGlobalIDFromUserMetadataID(id: string) {
const prefix = `${DocumentType.ROW}${SEPARATOR}${InternalTable.USER_METADATA}${SEPARATOR}`
if (!id || !id.includes(prefix)) {
return id
}
return id.split(prefix)[1]
}
/**
* 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) {
return `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}${newid()}`
}
export function generateAppUserID(prodAppId: string, userId: string) {
return `${prodAppId}${SEPARATOR}${userId}`
}
/**
* Generates a new role ID.
* @returns {string} The new role ID which the role doc can be stored under.
*/
export function generateRoleID(id?: any) {
return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}`
}
/**
* Generates a new dev info document ID - this is scoped to a user.
* @returns {string} The new dev info ID which info for dev (like api key) can be stored under.
*/
export const generateDevInfoID = (userId: any) => {
return `${DocumentType.DEV_INFO}${SEPARATOR}${userId}`
}
/**
* Generates a new plugin ID - to be used in the global DB.
* @returns {string} The new plugin ID which a plugin metadata document can be stored under.
*/
export const generatePluginID = (name: string) => {
return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
}

View file

@ -0,0 +1,2 @@
export * from "./ids"
export * from "./params"

View file

@ -0,0 +1,174 @@
import {
DocumentType,
InternalTable,
SEPARATOR,
UNICODE_MAX,
ViewName,
} from "../constants"
import { getProdAppID } from "./conversions"
/**
* If creating DB allDocs/query params with only a single top level ID this can be used, this
* is usually the case as most of our docs are top level e.g. tables, automations, users and so on.
* More complex cases such as link docs and rows which have multiple levels of IDs that their
* ID consists of need their own functions to build the allDocs parameters.
* @param {string} docType The type of document which input params are being built for, e.g. user,
* link, app, table and so on.
* @param {string|null} docId The ID of the document minus its type - this is only needed if looking
* for a singular document.
* @param {object} otherProps Add any other properties onto the request, e.g. include_docs.
* @returns {object} Parameters which can then be used with an allDocs request.
*/
export function getDocParams(
docType: string,
docId?: string | null,
otherProps: any = {}
) {
if (docId == null) {
docId = ""
}
return {
...otherProps,
startkey: `${docType}${SEPARATOR}${docId}`,
endkey: `${docType}${SEPARATOR}${docId}${UNICODE_MAX}`,
}
}
/**
* Gets the DB allDocs/query params for retrieving a row.
* @param {string|null} tableId The table in which the rows have been stored.
* @param {string|null} rowId The ID of the row which is being specifically queried for. This can be
* left null to get all the rows in the table.
* @param {object} otherProps Any other properties to add to the request.
* @returns {object} Parameters which can then be used with an allDocs request.
*/
export function getRowParams(
tableId?: string | null,
rowId?: string | null,
otherProps = {}
) {
if (tableId == null) {
return getDocParams(DocumentType.ROW, null, otherProps)
}
const endOfKey = rowId == null ? `${tableId}${SEPARATOR}` : rowId
return getDocParams(DocumentType.ROW, endOfKey, otherProps)
}
/**
* Retrieve the correct index for a view based on default design DB.
*/
export function getQueryIndex(viewName: ViewName) {
return `database/${viewName}`
}
/**
* Check if a given ID is that of a table.
* @returns {boolean}
*/
export const isTableId = (id: string) => {
// this includes datasource plus tables
return (
id &&
(id.startsWith(`${DocumentType.TABLE}${SEPARATOR}`) ||
id.startsWith(`${DocumentType.DATASOURCE_PLUS}${SEPARATOR}`))
)
}
/**
* Check if a given ID is that of a datasource or datasource plus.
* @returns {boolean}
*/
export const isDatasourceId = (id: string) => {
// this covers both datasources and datasource plus
return id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
}
/**
* Gets parameters for retrieving workspaces.
*/
export function getWorkspaceParams(id = "", otherProps = {}) {
return {
...otherProps,
startkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}`,
endkey: `${DocumentType.WORKSPACE}${SEPARATOR}${id}${UNICODE_MAX}`,
}
}
/**
* Gets parameters for retrieving users.
*/
export function getGlobalUserParams(globalId: any, otherProps: any = {}) {
if (!globalId) {
globalId = ""
}
const startkey = otherProps?.startkey
return {
...otherProps,
// need to include this incase pagination
startkey: startkey
? startkey
: `${DocumentType.USER}${SEPARATOR}${globalId}`,
endkey: `${DocumentType.USER}${SEPARATOR}${globalId}${UNICODE_MAX}`,
}
}
/**
* Gets parameters for retrieving users, this is a utility function for the getDocParams function.
*/
export function getUserMetadataParams(userId?: string | null, otherProps = {}) {
return getRowParams(InternalTable.USER_METADATA, userId, otherProps)
}
export function getUsersByAppParams(appId: any, otherProps: any = {}) {
const prodAppId = getProdAppID(appId)
return {
...otherProps,
startkey: prodAppId,
endkey: `${prodAppId}${UNICODE_MAX}`,
}
}
/**
* Gets parameters for retrieving templates. Owner ID must be specified, either global or a workspace level.
*/
export function getTemplateParams(
ownerId: any,
templateId: any,
otherProps = {}
) {
if (!templateId) {
templateId = ""
}
let final
if (templateId) {
final = templateId
} else {
final = `${DocumentType.TEMPLATE}${SEPARATOR}${ownerId}${SEPARATOR}`
}
return {
...otherProps,
startkey: final,
endkey: `${final}${UNICODE_MAX}`,
}
}
/**
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
*/
export function getRoleParams(roleId?: string | null, otherProps = {}) {
return getDocParams(DocumentType.ROLE, roleId, otherProps)
}
export function getStartEndKeyURL(baseKey: any, tenantId?: string) {
const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : ""
return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"`
}
/**
* Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
*/
export const getPluginParams = (pluginId?: string | null, otherProps = {}) => {
return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
}

View file

@ -1,4 +1,4 @@
import { testEnv } from "../../../../../tests"
import { testEnv } from "../../../../../tests/extra"
import PosthogProcessor from "../PosthogProcessor"
import { Event, IdentityType, Hosting } from "@budibase/types"
const tk = require("timekeeper")

View file

@ -1,4 +1,5 @@
import { structures, testEnv, mocks } from "../../../../../tests"
import { structures, mocks } from "../../../../../tests"
import { testEnv } from "../../../../../tests/extra"
import { SSOAuthDetails, User } from "@budibase/types"
import { HTTPError } from "../../../../errors"

View file

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`migrations should match snapshot 1`] = `
Object {
{
"_id": "migrations",
"_rev": "1-2f64479842a0513aa8b97f356b0b9127",
"createdAt": "2020-01-01T00:00:00.000Z",

View file

@ -1,4 +1,4 @@
import { testEnv, DBTestConfiguration } from "../../../tests"
import { testEnv, DBTestConfiguration } from "../../../tests/extra"
import * as migrations from "../index"
import * as context from "../../context"
import { MigrationType } from "@budibase/types"

View file

@ -1,6 +1,6 @@
import * as app from "../app"
import { getAppFileUrl } from "../app"
import { testEnv } from "../../../../tests"
import { testEnv } from "../../../../tests/extra"
describe("app", () => {
beforeEach(() => {

View file

@ -1,5 +1,5 @@
import * as global from "../global"
import { testEnv } from "../../../../tests"
import { testEnv } from "../../../../tests/extra"
describe("global", () => {
describe("getGlobalFileUrl", () => {

View file

@ -1,5 +1,6 @@
import * as plugins from "../plugins"
import { structures, testEnv } from "../../../../tests"
import { structures } from "../../../../tests"
import { testEnv } from "../../../../tests/extra"
describe("plugins", () => {
describe("enrichPluginURLs", () => {

View file

@ -1,4 +1,5 @@
import { DBTestConfiguration, structures } from "../../../tests"
import { structures } from "../../../tests"
import { DBTestConfiguration } from "../../../tests/extra"
import * as tenants from "../tenants"
describe("tenants", () => {

View file

@ -12,7 +12,7 @@ export enum SecretOption {
ENCRYPTION = "encryption",
}
function getSecret(secretOption: SecretOption): string {
export function getSecret(secretOption: SecretOption): string {
let secret, secretName
switch (secretOption) {
case SecretOption.ENCRYPTION:

View file

@ -0,0 +1,31 @@
import { encrypt, decrypt, SecretOption, getSecret } from "../encryption"
import env from "../../environment"
describe("encryption", () => {
it("should throw an error if API encryption key is not set", () => {
const jwt = getSecret(SecretOption.API)
expect(jwt).toBe(env.JWT_SECRET)
})
it("should throw an error if encryption key is not set", () => {
expect(() => getSecret(SecretOption.ENCRYPTION)).toThrow(
'Secret "ENCRYPTION_KEY" has not been set in environment.'
)
})
it("should encrypt and decrypt a string using API encryption key", () => {
env._set("API_ENCRYPTION_KEY", "api_secret")
const plaintext = "budibase"
const apiEncrypted = encrypt(plaintext, SecretOption.API)
const decrypted = decrypt(apiEncrypted, SecretOption.API)
expect(decrypted).toEqual(plaintext)
})
it("should encrypt and decrypt a string using encryption key", () => {
env._set("ENCRYPTION_KEY", "normal_secret")
const plaintext = "budibase"
const encryptionEncrypted = encrypt(plaintext, SecretOption.ENCRYPTION)
const decrypted = decrypt(encryptionEncrypted, SecretOption.ENCRYPTION)
expect(decrypted).toEqual(plaintext)
})
})

View file

@ -3,8 +3,8 @@ import {
getTenantId,
getTenantIDFromAppID,
isMultiTenant,
getPlatformURL,
} from "../context"
import env from "../environment"
import {
BBContext,
TenantResolutionStrategy,
@ -93,7 +93,7 @@ export const getTenantIDFromCtx = (
// subdomain
if (isAllowed(TenantResolutionStrategy.SUBDOMAIN)) {
// e.g. budibase.app or local.com:10000
const platformHost = new URL(env.PLATFORM_URL).host.split(":")[0]
const platformHost = new URL(getPlatformURL()).host.split(":")[0]
// e.g. tenant.budibase.app or tenant.local.com
const requestHost = ctx.host
// parse the tenant id from the difference

View file

@ -0,0 +1,184 @@
import { TenantResolutionStrategy } from "@budibase/types"
import { addTenantToUrl, isUserInAppTenant, getTenantIDFromCtx } from "../"
import { isMultiTenant, getTenantIDFromAppID } from "../../context"
jest.mock("../../context", () => ({
getTenantId: jest.fn(() => "budibase"),
isMultiTenant: jest.fn(() => true),
getTenantIDFromAppID: jest.fn(),
getPlatformURL: jest.fn(() => "https://app.com"),
DEFAULT_TENANT_ID: "default",
}))
const mockedIsMultiTenant = isMultiTenant as jest.MockedFunction<
typeof isMultiTenant
>
const mockedGetTenantIDFromAppID = getTenantIDFromAppID as jest.MockedFunction<
typeof getTenantIDFromAppID
>
describe("addTenantToUrl", () => {
it("should append tenantId parameter to the URL", () => {
const url = "https://budibase.com"
const expectedUrl = "https://budibase.com?tenantId=budibase"
expect(addTenantToUrl(url)).toEqual(expectedUrl)
})
it("should append tenantId parameter to the URL query string", () => {
const url = "https://budibase.com?var=test"
const expectedUrl = "https://budibase.com?var=test&tenantId=budibase"
expect(addTenantToUrl(url)).toEqual(expectedUrl)
})
it("should not append tenantId parameter to the URL if isMultiTenant is false", () => {
mockedIsMultiTenant.mockImplementation(() => false)
const url = "https://budibase.com"
const expectedUrl = "https://budibase.com"
expect(addTenantToUrl(url)).toEqual(expectedUrl)
})
})
describe("isUserInAppTenant", () => {
mockedGetTenantIDFromAppID.mockImplementation(() => "budibase")
const mockUser = { tenantId: "budibase" }
it("returns true if user tenant ID matches app tenant ID", () => {
const appId = "app-budibase"
const result = isUserInAppTenant(appId, mockUser)
expect(result).toBe(true)
})
it("uses default tenant ID if user is not provided", () => {
const appId = "app-budibase"
const result = isUserInAppTenant(appId)
expect(result).toBe(true)
})
it("uses default tenant ID if app tenant ID is not found", () => {
const appId = "not-budibase-app"
const result = isUserInAppTenant(appId, mockUser)
expect(result).toBe(true)
})
it("returns false if user tenant ID does not match app tenant ID", () => {
const appId = "app-budibase"
mockedGetTenantIDFromAppID.mockImplementation(() => "not-budibase")
const result = isUserInAppTenant(appId, mockUser)
expect(result).toBe(false)
})
})
let mockOpts: any = {}
function createCtx(opts: {
originalUrl?: string
headers?: Record<string, string>
qsTenantId?: string
userTenantId?: string
host?: string
path?: string
}) {
const createdCtx: any = {
originalUrl: opts.originalUrl || "budibase.com",
matched: [{ name: "name" }],
throw: jest.fn(),
request: { headers: {} },
}
if (opts.headers) {
createdCtx.request.headers = opts.headers
}
if (opts.qsTenantId) {
createdCtx.request.query = { tenantId: opts.qsTenantId }
}
if (opts.userTenantId) {
createdCtx.user = { tenantId: opts.userTenantId }
}
if (opts.host) {
createdCtx.host = opts.host
}
if (opts.path) {
createdCtx.matched = [
{
paramNames: [{ name: "tenantId" }],
params: () => ({ tenantId: opts.path }),
captures: jest.fn(),
},
]
}
return createdCtx as any
}
describe("getTenantIDFromCtx", () => {
describe("when tenant can be found", () => {
it("returns the tenant ID from the user object", () => {
mockedIsMultiTenant.mockImplementation(() => true)
const ctx = createCtx({ userTenantId: "budibase" })
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("budibase")
})
it("returns the tenant ID from the header", () => {
mockedIsMultiTenant.mockImplementation(() => true)
const ctx = createCtx({ headers: { "x-budibase-tenant-id": "budibase" } })
mockOpts = { includeStrategies: [TenantResolutionStrategy.HEADER] }
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("budibase")
})
it("returns the tenant ID from the query param", () => {
mockedIsMultiTenant.mockImplementation(() => true)
mockOpts = { includeStrategies: [TenantResolutionStrategy.QUERY] }
const ctx = createCtx({ qsTenantId: "budibase" })
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("budibase")
})
it("returns the tenant ID from the subdomain", () => {
mockedIsMultiTenant.mockImplementation(() => true)
const ctx = createCtx({ host: "bb.app.com" })
mockOpts = { includeStrategies: [TenantResolutionStrategy.SUBDOMAIN] }
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("bb")
})
it("returns the tenant ID from the path", () => {
mockedIsMultiTenant.mockImplementation(() => true)
const ctx = createCtx({ path: "bb" })
mockOpts = { includeStrategies: [TenantResolutionStrategy.PATH] }
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("bb")
})
})
describe("when tenant cannot be found", () => {
it("throws a 403 error if allowNoTenant is false", () => {
const ctx = createCtx({})
mockOpts = {
allowNoTenant: false,
excludeStrategies: [
TenantResolutionStrategy.QUERY,
TenantResolutionStrategy.SUBDOMAIN,
TenantResolutionStrategy.PATH,
],
}
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
expect(ctx.throw).toBeCalledTimes(1)
expect(ctx.throw).toBeCalledWith(403, "Tenant id not set")
})
it("returns null if allowNoTenant is true", () => {
const ctx = createCtx({})
mockOpts = {
allowNoTenant: true,
excludeStrategies: [
TenantResolutionStrategy.QUERY,
TenantResolutionStrategy.SUBDOMAIN,
TenantResolutionStrategy.PATH,
],
}
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
})
})
it("returns the default tenant ID when isMultiTenant() returns false", () => {
mockedIsMultiTenant.mockImplementation(() => false)
const ctx = createCtx({})
expect(getTenantIDFromCtx(ctx, mockOpts)).toEqual("default")
})
})

View file

@ -1,5 +1,5 @@
import env from "../environment"
export * from "../newid"
export * from "../docIds/newid"
const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt")
const SALT_ROUNDS = env.SALT_ROUNDS || 10

View file

@ -1,4 +1,5 @@
import { structures, DBTestConfiguration } from "../../../tests"
import { structures } from "../../../tests"
import { DBTestConfiguration } from "../../../tests/extra"
import * as utils from "../../utils"
import * as db from "../../db"
import { Header } from "../../constants"

View file

@ -0,0 +1,34 @@
export enum LogLevel {
TRACE = "trace",
DEBUG = "debug",
INFO = "info",
WARN = "warn",
ERROR = "error",
}
const LOG_INDEX: { [key in LogLevel]: number } = {
[LogLevel.TRACE]: 1,
[LogLevel.DEBUG]: 2,
[LogLevel.INFO]: 3,
[LogLevel.WARN]: 4,
[LogLevel.ERROR]: 5,
}
const setIndex = LOG_INDEX[process.env.LOG_LEVEL as LogLevel]
if (setIndex > LOG_INDEX.trace) {
global.console.trace = jest.fn()
}
if (setIndex > LOG_INDEX.debug) {
global.console.debug = jest.fn()
}
if (setIndex > LOG_INDEX.info) {
global.console.info = jest.fn()
global.console.log = jest.fn()
}
if (setIndex > LOG_INDEX.warn) {
global.console.warn = jest.fn()
}

View file

@ -1,9 +1,6 @@
export * as mocks from "./mocks"
export * as structures from "./structures"
export { generator } from "./structures"
export * as testEnv from "./testEnv"
export * as testContainerUtils from "./testContainerUtils"
export * from "./jestUtils"
export { default as DBTestConfiguration } from "./DBTestConfiguration"

View file

@ -0,0 +1,3 @@
jest.mock("../../../../src/logging/alerts")
import * as _alerts from "../../../../src/logging/alerts"
export const alerts = jest.mocked(_alerts)

View file

@ -0,0 +1,123 @@
beforeAll(async () => {
const processors = await import("../../../../src/events/processors")
const events = await import("../../../../src/events")
jest.spyOn(processors.analyticsProcessor, "processEvent")
jest.spyOn(events.identification, "identifyTenantGroup")
jest.spyOn(events.identification, "identifyUser")
jest.spyOn(events.backfill, "appSucceeded")
jest.spyOn(events.backfill, "tenantSucceeded")
jest.spyOn(events.account, "created")
jest.spyOn(events.account, "deleted")
jest.spyOn(events.account, "verified")
jest.spyOn(events.app, "created")
jest.spyOn(events.app, "updated")
jest.spyOn(events.app, "deleted")
jest.spyOn(events.app, "published")
jest.spyOn(events.app, "unpublished")
jest.spyOn(events.app, "templateImported")
jest.spyOn(events.app, "fileImported")
jest.spyOn(events.app, "versionUpdated")
jest.spyOn(events.app, "versionReverted")
jest.spyOn(events.app, "reverted")
jest.spyOn(events.app, "exported")
jest.spyOn(events.auth, "login")
jest.spyOn(events.auth, "logout")
jest.spyOn(events.auth, "SSOCreated")
jest.spyOn(events.auth, "SSOUpdated")
jest.spyOn(events.auth, "SSOActivated")
jest.spyOn(events.auth, "SSODeactivated")
jest.spyOn(events.automation, "created")
jest.spyOn(events.automation, "deleted")
jest.spyOn(events.automation, "tested")
jest.spyOn(events.automation, "stepCreated")
jest.spyOn(events.automation, "stepDeleted")
jest.spyOn(events.automation, "triggerUpdated")
jest.spyOn(events.datasource, "created")
jest.spyOn(events.datasource, "updated")
jest.spyOn(events.datasource, "deleted")
jest.spyOn(events.email, "SMTPCreated")
jest.spyOn(events.email, "SMTPUpdated")
jest.spyOn(events.layout, "created")
jest.spyOn(events.layout, "deleted")
jest.spyOn(events.org, "nameUpdated")
jest.spyOn(events.org, "logoUpdated")
jest.spyOn(events.org, "platformURLUpdated")
jest.spyOn(events.org, "analyticsOptOut")
jest.spyOn(events.installation, "versionChecked")
jest.spyOn(events.query, "created")
jest.spyOn(events.query, "updated")
jest.spyOn(events.query, "deleted")
jest.spyOn(events.query, "imported")
jest.spyOn(events.query, "previewed")
jest.spyOn(events.role, "created")
jest.spyOn(events.role, "updated")
jest.spyOn(events.role, "deleted")
jest.spyOn(events.role, "assigned")
jest.spyOn(events.role, "unassigned")
jest.spyOn(events.rows, "imported")
jest.spyOn(events.rows, "created")
jest.spyOn(events.screen, "created")
jest.spyOn(events.screen, "deleted")
jest.spyOn(events.user, "created")
jest.spyOn(events.user, "updated")
jest.spyOn(events.user, "deleted")
jest.spyOn(events.user, "permissionAdminAssigned")
jest.spyOn(events.user, "permissionAdminRemoved")
jest.spyOn(events.user, "permissionBuilderAssigned")
jest.spyOn(events.user, "permissionBuilderRemoved")
jest.spyOn(events.user, "invited")
jest.spyOn(events.user, "inviteAccepted")
jest.spyOn(events.user, "passwordForceReset")
jest.spyOn(events.user, "passwordUpdated")
jest.spyOn(events.user, "passwordResetRequested")
jest.spyOn(events.user, "passwordReset")
jest.spyOn(events.group, "created")
jest.spyOn(events.group, "updated")
jest.spyOn(events.group, "deleted")
jest.spyOn(events.group, "usersAdded")
jest.spyOn(events.group, "usersDeleted")
jest.spyOn(events.group, "createdOnboarding")
jest.spyOn(events.group, "permissionsEdited")
jest.spyOn(events.serve, "servedBuilder")
jest.spyOn(events.serve, "servedApp")
jest.spyOn(events.serve, "servedAppPreview")
jest.spyOn(events.table, "created")
jest.spyOn(events.table, "updated")
jest.spyOn(events.table, "deleted")
jest.spyOn(events.table, "exported")
jest.spyOn(events.table, "imported")
jest.spyOn(events.view, "created")
jest.spyOn(events.view, "updated")
jest.spyOn(events.view, "deleted")
jest.spyOn(events.view, "exported")
jest.spyOn(events.view, "filterCreated")
jest.spyOn(events.view, "filterUpdated")
jest.spyOn(events.view, "filterDeleted")
jest.spyOn(events.view, "calculationCreated")
jest.spyOn(events.view, "calculationUpdated")
jest.spyOn(events.view, "calculationDeleted")
jest.spyOn(events.plugin, "init")
jest.spyOn(events.plugin, "imported")
jest.spyOn(events.plugin, "deleted")
})

View file

@ -0,0 +1,17 @@
const mockFetch = jest.fn((url: any, opts: any) => {
const fetch = jest.requireActual("node-fetch")
const env = jest.requireActual("../../../../src/environment").default
if (url.includes(env.COUCH_DB_URL)) {
return fetch(url, opts)
}
return undefined
})
const enable = () => {
jest.mock("node-fetch", () => mockFetch)
}
export default {
...mockFetch,
enable,
}

View file

@ -1,10 +1,10 @@
jest.mock("../../../src/accounts")
import * as _accounts from "../../../src/accounts"
jest.mock("../../../../src/accounts")
import * as _accounts from "../../../../src/accounts"
export const accounts = jest.mocked(_accounts)
export * as date from "./date"
export * as licenses from "./licenses"
export { default as fetch } from "./fetch"
export * from "./alerts"
import "./posthog"
import "./events"
import "./posthog"

View file

@ -1,5 +1,5 @@
import { generator, uuid } from "."
import * as db from "../../../src/db/utils"
import { generateGlobalUserID } from "../../../../src/docIds"
import {
Account,
AccountSSOProvider,
@ -39,7 +39,7 @@ export const cloudAccount = (): CloudAccount => {
return {
...account(),
hosting: Hosting.CLOUD,
budibaseUserId: db.generateGlobalUserID(),
budibaseUserId: generateGlobalUserID(),
}
}

View file

@ -1,6 +1,6 @@
import { generator } from "."
import { App } from "@budibase/types"
import { DEFAULT_TENANT_ID, DocumentType } from "../../../src/constants"
import { DEFAULT_TENANT_ID, DocumentType } from "../../../../src/constants"
export function app(id: string): App {
return {

View file

@ -1,5 +1,5 @@
import { structures } from ".."
import { newid } from "../../../src/newid"
import { newid } from "../../../../src/docIds/newid"
export function id() {
return `db_${newid()}`

View file

@ -1,4 +1,4 @@
import { newid } from "../../../src/newid"
import { newid } from "../../../../src/docIds/newid"
export function id() {
return `tenant-${newid()}`

View file

@ -1,5 +1,5 @@
import "./mocks"
import * as structures from "./structures"
import "../core/utilities/mocks"
import * as structures from "../core/utilities/structures"
import * as testEnv from "./testEnv"
import * as context from "../../src/context"

View file

@ -0,0 +1,2 @@
export * as testEnv from "./testEnv"
export { default as DBTestConfiguration } from "./DBTestConfiguration"

View file

@ -1,6 +1,6 @@
import env from "../../src/environment"
import * as context from "../../src/context"
import * as structures from "./structures"
import * as structures from "../core/utilities/structures"
// TENANCY

View file

@ -1 +1 @@
export * from "./utilities"
export * from "./core/utilities"

View file

@ -1,6 +1,7 @@
import "./core/logging"
import env from "../src/environment"
import { cleanup } from "../src/timers"
import { mocks, testContainerUtils } from "./utilities"
import { mocks, testContainerUtils } from "./core/utilities"
// must explicitly enable fetch mock
mocks.fetch.enable()

View file

@ -1,3 +0,0 @@
jest.mock("../../../src/logging/alerts")
import * as _alerts from "../../../src/logging/alerts"
export const alerts = jest.mocked(_alerts)

View file

@ -1,122 +0,0 @@
import * as processors from "../../../src/events/processors"
import * as events from "../../../src/events"
jest.spyOn(processors.analyticsProcessor, "processEvent")
jest.spyOn(events.identification, "identifyTenantGroup")
jest.spyOn(events.identification, "identifyUser")
jest.spyOn(events.backfill, "appSucceeded")
jest.spyOn(events.backfill, "tenantSucceeded")
jest.spyOn(events.account, "created")
jest.spyOn(events.account, "deleted")
jest.spyOn(events.account, "verified")
jest.spyOn(events.app, "created")
jest.spyOn(events.app, "updated")
jest.spyOn(events.app, "deleted")
jest.spyOn(events.app, "published")
jest.spyOn(events.app, "unpublished")
jest.spyOn(events.app, "templateImported")
jest.spyOn(events.app, "fileImported")
jest.spyOn(events.app, "versionUpdated")
jest.spyOn(events.app, "versionReverted")
jest.spyOn(events.app, "reverted")
jest.spyOn(events.app, "exported")
jest.spyOn(events.auth, "login")
jest.spyOn(events.auth, "logout")
jest.spyOn(events.auth, "SSOCreated")
jest.spyOn(events.auth, "SSOUpdated")
jest.spyOn(events.auth, "SSOActivated")
jest.spyOn(events.auth, "SSODeactivated")
jest.spyOn(events.automation, "created")
jest.spyOn(events.automation, "deleted")
jest.spyOn(events.automation, "tested")
jest.spyOn(events.automation, "stepCreated")
jest.spyOn(events.automation, "stepDeleted")
jest.spyOn(events.automation, "triggerUpdated")
jest.spyOn(events.datasource, "created")
jest.spyOn(events.datasource, "updated")
jest.spyOn(events.datasource, "deleted")
jest.spyOn(events.email, "SMTPCreated")
jest.spyOn(events.email, "SMTPUpdated")
jest.spyOn(events.layout, "created")
jest.spyOn(events.layout, "deleted")
jest.spyOn(events.org, "nameUpdated")
jest.spyOn(events.org, "logoUpdated")
jest.spyOn(events.org, "platformURLUpdated")
jest.spyOn(events.org, "analyticsOptOut")
jest.spyOn(events.installation, "versionChecked")
jest.spyOn(events.query, "created")
jest.spyOn(events.query, "updated")
jest.spyOn(events.query, "deleted")
jest.spyOn(events.query, "imported")
jest.spyOn(events.query, "previewed")
jest.spyOn(events.role, "created")
jest.spyOn(events.role, "updated")
jest.spyOn(events.role, "deleted")
jest.spyOn(events.role, "assigned")
jest.spyOn(events.role, "unassigned")
jest.spyOn(events.rows, "imported")
jest.spyOn(events.rows, "created")
jest.spyOn(events.screen, "created")
jest.spyOn(events.screen, "deleted")
jest.spyOn(events.user, "created")
jest.spyOn(events.user, "updated")
jest.spyOn(events.user, "deleted")
jest.spyOn(events.user, "permissionAdminAssigned")
jest.spyOn(events.user, "permissionAdminRemoved")
jest.spyOn(events.user, "permissionBuilderAssigned")
jest.spyOn(events.user, "permissionBuilderRemoved")
jest.spyOn(events.user, "invited")
jest.spyOn(events.user, "inviteAccepted")
jest.spyOn(events.user, "passwordForceReset")
jest.spyOn(events.user, "passwordUpdated")
jest.spyOn(events.user, "passwordResetRequested")
jest.spyOn(events.user, "passwordReset")
jest.spyOn(events.group, "created")
jest.spyOn(events.group, "updated")
jest.spyOn(events.group, "deleted")
jest.spyOn(events.group, "usersAdded")
jest.spyOn(events.group, "usersDeleted")
jest.spyOn(events.group, "createdOnboarding")
jest.spyOn(events.group, "permissionsEdited")
jest.spyOn(events.serve, "servedBuilder")
jest.spyOn(events.serve, "servedApp")
jest.spyOn(events.serve, "servedAppPreview")
jest.spyOn(events.table, "created")
jest.spyOn(events.table, "updated")
jest.spyOn(events.table, "deleted")
jest.spyOn(events.table, "exported")
jest.spyOn(events.table, "imported")
jest.spyOn(events.view, "created")
jest.spyOn(events.view, "updated")
jest.spyOn(events.view, "deleted")
jest.spyOn(events.view, "exported")
jest.spyOn(events.view, "filterCreated")
jest.spyOn(events.view, "filterUpdated")
jest.spyOn(events.view, "filterDeleted")
jest.spyOn(events.view, "calculationCreated")
jest.spyOn(events.view, "calculationUpdated")
jest.spyOn(events.view, "calculationDeleted")
jest.spyOn(events.plugin, "init")
jest.spyOn(events.plugin, "imported")
jest.spyOn(events.plugin, "deleted")

View file

@ -1,10 +0,0 @@
const mockFetch = jest.fn()
const enable = () => {
jest.mock("node-fetch", () => mockFetch)
}
export default {
...mockFetch,
enable,
}

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,7 @@ describe("syncRows", () => {
// app 1
const app1 = config.app
await context.doInAppContext(app1.appId, async () => {
await context.doInAppContext(app1!.appId, async () => {
await config.createTable()
await config.createRow()
})
@ -43,7 +43,7 @@ describe("syncRows", () => {
usageDoc = await quotas.getQuotaUsage()
expect(usageDoc.usageQuota.rows).toEqual(3)
expect(
usageDoc.apps?.[dbCore.getProdAppID(app1.appId)].usageQuota.rows
usageDoc.apps?.[dbCore.getProdAppID(app1!.appId)].usageQuota.rows
).toEqual(1)
expect(
usageDoc.apps?.[dbCore.getProdAppID(app2.appId)].usageQuota.rows