Browse Source

Tests complete + backwards compatibility for deployment

pull/5001/head
Rory Powell 4 years ago
parent
commit
180e37b9f1
  1. 4
      charts/budibase/templates/app-service-deployment.yaml
  2. 4
      charts/budibase/values.yaml
  3. 4
      packages/backend-core/src/db/views.js
  4. 3
      packages/backend-core/src/environment.js
  5. 52
      packages/backend-core/src/featureFlags/index.js
  6. 1
      packages/backend-core/src/index.js
  7. 2
      packages/backend-core/src/middleware/passport/google.js
  8. 2
      packages/backend-core/src/middleware/passport/oidc.js
  9. 7
      packages/backend-core/src/middleware/passport/tests/google.spec.js
  10. 4
      packages/backend-core/src/middleware/passport/tests/oidc.spec.js
  11. 4
      packages/backend-core/src/utils.js
  12. 935
      packages/bbui/yarn.lock
  13. 1284
      packages/client/yarn.lock
  14. 1336
      packages/frontend-core/yarn.lock
  15. 5
      packages/server/package.json
  16. 4
      packages/server/src/api/controllers/application.ts
  17. 6
      packages/server/src/api/routes/application.ts
  18. 0
      packages/server/src/api/routes/index.ts
  19. 2
      packages/server/src/api/routes/public/index.ts
  20. 4
      packages/server/src/api/routes/row.ts
  21. 6
      packages/server/src/api/routes/static.ts
  22. 67
      packages/server/src/api/routes/tests/utilities/TestFunctions.ts
  23. 3
      packages/server/src/automations/steps/createRow.ts
  24. 2
      packages/server/src/automations/steps/deleteRow.ts
  25. 1
      packages/server/src/automations/tests/automation.spec.js
  26. 19
      packages/server/src/automations/tests/createRow.spec.ts
  27. 8
      packages/server/src/automations/tests/deleteRow.spec.ts
  28. 2
      packages/server/src/automations/tests/utilities/index.js
  29. 2
      packages/server/src/environment.js
  30. 32
      packages/server/src/middleware/authorized.ts
  31. 134
      packages/server/src/middleware/tests/usageQuota.spec.js
  32. 2
      packages/server/src/migrations/functions/tests/quotas1.spec.js
  33. 37
      packages/server/src/migrations/functions/usageQuotas/tests/syncApps.spec.js
  34. 32
      packages/server/src/migrations/functions/usageQuotas/tests/syncApps.spec.ts
  35. 43
      packages/server/src/migrations/functions/usageQuotas/tests/syncRows.spec.js
  36. 38
      packages/server/src/migrations/functions/usageQuotas/tests/syncRows.spec.ts
  37. 2
      packages/server/src/module.d.ts
  38. 72
      packages/server/src/utilities/tests/usageQuota/usageQuota.spec.js
  39. 10
      packages/server/tsconfig.build.json
  40. 4
      packages/server/tsconfig.json
  41. 27
      packages/server/yarn.lock
  42. 3
      packages/worker/src/api/routes/tests/auth.spec.js
  43. 1196
      packages/worker/yarn.lock

4
charts/budibase/templates/app-service-deployment.yaml

@ -98,10 +98,6 @@ spec:
value: http://worker-service:{{ .Values.services.worker.port }}
- name: PLATFORM_URL
value: {{ .Values.globals.platformUrl | quote }}
- name: USE_QUOTAS
value: {{ .Values.globals.useQuotas | quote }}
- name: EXCLUDE_QUOTAS_TENANTS
value: {{ .Values.globals.excludeQuotasTenants | quote }}
- name: ACCOUNT_PORTAL_URL
value: {{ .Values.globals.accountPortalUrl | quote }}
- name: ACCOUNT_PORTAL_API_KEY

4
charts/budibase/values.yaml

@ -93,15 +93,13 @@ globals:
logLevel: info
selfHosted: "1" # set to 0 for budibase cloud environment, set to 1 for self-hosted setup
multiTenancy: "0" # set to 0 to disable multiple orgs, set to 1 to enable multiple orgs
useQuotas: "0"
excludeQuotasTenants: "" # comma seperated list of tenants to exclude from quotas
accountPortalUrl: ""
accountPortalApiKey: ""
cookieDomain: ""
platformUrl: ""
httpMigrations: "0"
google:
clientId: ""
clientId: ""
secret: ""
createSecrets: true # creates an internal API key, JWT secrets and redis password for you

4
packages/backend-core/src/db/views.js

@ -56,7 +56,8 @@ exports.createApiKeyView = async () => {
await db.put(designDoc)
}
exports.createUserBuildersView = async db => {
exports.createUserBuildersView = async () => {
const db = getGlobalDB()
let designDoc
try {
designDoc = await db.get("_design/database")
@ -82,6 +83,7 @@ exports.queryGlobalView = async (viewName, params, db = null) => {
const CreateFuncByName = {
[ViewNames.USER_BY_EMAIL]: exports.createUserEmailView,
[ViewNames.BY_API_KEY]: exports.createApiKeyView,
[ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView,
}
// can pass DB in if working with something specific
if (!db) {

3
packages/backend-core/src/environment.js

@ -28,8 +28,7 @@ module.exports = {
SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED),
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
PLATFORM_URL: process.env.PLATFORM_URL,
USE_QUOTAS: process.env.USE_QUOTAS,
EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
TENANT_FEATURE_FLAGS: process.env.TENANT_FEATURE_FLAGS,
isTest,
_set(key, value) {
process.env[key] = value

52
packages/backend-core/src/featureFlags/index.js

@ -0,0 +1,52 @@
const env = require("../environment")
const tenancy = require("../tenancy")
/**
* Read the TENANT_FEATURE_FLAGS env var and return an array of features flags for each tenant.
* The env var is formatted as:
* tenant1:feature1:feature2,tenant2:feature1
*/
const getFeatureFlags = () => {
if (!env.TENANT_FEATURE_FLAGS) {
return
}
const tenantFeatureFlags = {}
env.TENANT_FEATURE_FLAGS.split(",").forEach(tenantToFeatures => {
const [tenantId, ...features] = tenantToFeatures.split(":")
features.forEach(feature => {
if (!tenantFeatureFlags[tenantId]) {
tenantFeatureFlags[tenantId] = []
}
tenantFeatureFlags[tenantId].push(feature)
})
})
return tenantFeatureFlags
}
const TENANT_FEATURE_FLAGS = getFeatureFlags()
exports.isEnabled = featureFlag => {
const tenantId = tenancy.getTenantId()
return (
TENANT_FEATURE_FLAGS &&
TENANT_FEATURE_FLAGS[tenantId] &&
TENANT_FEATURE_FLAGS[tenantId].includes(featureFlag)
)
}
exports.getTenantFeatureFlags = tenantId => {
if (TENANT_FEATURE_FLAGS && TENANT_FEATURE_FLAGS[tenantId]) {
return TENANT_FEATURE_FLAGS[tenantId]
}
return []
}
exports.FeatureFlag = {
LICENSING: "LICENSING",
}

1
packages/backend-core/src/index.js

@ -19,4 +19,5 @@ module.exports = {
env: require("./environment"),
accounts: require("./cloud/accounts"),
tenancy: require("./tenancy"),
featureFlags: require("./featureFlags"),
}

2
packages/backend-core/src/middleware/passport/google.js

@ -55,4 +55,4 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
}
}
// expose for testing
exports.authenticate = buildVerifyFn
exports.buildVerifyFn = buildVerifyFn

2
packages/backend-core/src/middleware/passport/oidc.js

@ -129,4 +129,4 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
}
// expose for testing
exports.authenticate = buildVerifyFn
exports.buildVerifyFn = buildVerifyFn

7
packages/backend-core/src/middleware/passport/tests/google.spec.js

@ -58,8 +58,10 @@ describe("google", () => {
it("delegates authentication to third party common", async () => {
const google = require("../google")
const mockSaveUserFn = jest.fn()
const authenticate = await google.buildVerifyFn(mockSaveUserFn)
await google.authenticate(
await authenticate(
data.accessToken,
data.refreshToken,
profile,
@ -69,7 +71,8 @@ describe("google", () => {
expect(authenticateThirdParty).toHaveBeenCalledWith(
user,
true,
mockDone)
mockDone,
mockSaveUserFn)
})
})
})

4
packages/backend-core/src/middleware/passport/tests/oidc.spec.js

@ -83,8 +83,10 @@ describe("oidc", () => {
async function doAuthenticate() {
const oidc = require("../oidc")
const mockSaveUserFn = jest.fn()
const authenticate = await oidc.buildVerifyFn(mockSaveUserFn)
await oidc.authenticate(
await authenticate(
issuer,
sub,
profile,

4
packages/backend-core/src/utils.js

@ -147,7 +147,9 @@ exports.getGlobalUserByEmail = async email => {
}
exports.getBuildersCount = async () => {
const builders = await queryGlobalView(ViewNames.BUILDERS)
const builders = await queryGlobalView(ViewNames.USER_BY_BUILDERS, {
include_docs: false,
})
return builders.total_rows
}

935
packages/bbui/yarn.lock

File diff suppressed because it is too large

1284
packages/client/yarn.lock

File diff suppressed because it is too large

1336
packages/frontend-core/yarn.lock

File diff suppressed because it is too large

5
packages/server/package.json

@ -9,7 +9,7 @@
"url": "https://github.com/Budibase/budibase.git"
},
"scripts": {
"build": "rimraf dist/ && tsc && mv dist/src/* dist/ && rmdir dist/src/ && yarn postbuild",
"build": "rimraf dist/ && tsc -p tsconfig.build.json && mv dist/src/* dist/ && rmdir dist/src/ && yarn postbuild",
"postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
"test": "jest --coverage --maxWorkers=2",
"test:watch": "jest --watch",
@ -81,6 +81,7 @@
"@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1",
"@sentry/node": "6.17.7",
"@types/koa__router": "^8.0.11",
"airtable": "0.10.1",
"arangojs": "7.2.0",
"aws-sdk": "^2.767.0",
@ -146,7 +147,7 @@
"@types/apidoc": "^0.50.0",
"@types/bull": "^3.15.1",
"@types/google-spreadsheet": "^3.1.5",
"@types/jest": "^26.0.23",
"@types/jest": "^27.4.1",
"@types/koa": "^2.13.3",
"@types/koa-router": "^7.4.2",
"@types/lodash": "^4.14.179",

4
packages/server/src/api/controllers/application.ts

@ -389,7 +389,7 @@ const destroyApp = async (ctx: any) => {
const db = getAppDB()
const result = await db.destroy()
if (ctx.query.unpublish) {
if (ctx.query?.unpublish) {
await quotas.removePublishedApp()
} else {
await quotas.removeApp()
@ -408,7 +408,7 @@ const destroyApp = async (ctx: any) => {
}
const preDestroyApp = async (ctx: any) => {
const rows = await getUniqueRows([ctx.appId])
const rows = await getUniqueRows([ctx.params.appId])
ctx.rowCount = rows.length
}

6
packages/server/src/api/routes/application.ts

@ -1,9 +1,9 @@
const Router = require("@koa/router")
import Router from "@koa/router"
import * as controller from "../controllers/application"
import authorized from "../../middleware/authorized"
const { BUILDER } = require("@budibase/backend-core/permissions")
import { BUILDER } from "@budibase/backend-core/permissions"
const router = Router()
const router = new Router()
router
.post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync)

0
packages/server/src/api/routes/index.js → packages/server/src/api/routes/index.ts

2
packages/server/src/api/routes/public/index.ts

@ -127,4 +127,4 @@ applyRoutes(queryEndpoints, PermissionTypes.QUERY, "queryId")
// needs to be applied last for routing purposes, don't override other endpoints
applyRoutes(rowEndpoints, PermissionTypes.TABLE, "tableId", "rowId")
module.exports = publicRouter
export default publicRouter

4
packages/server/src/api/routes/row.ts

@ -1,4 +1,4 @@
const Router = require("@koa/router")
import Router from "@koa/router"
import * as rowController from "../controllers/row"
import authorized from "../../middleware/authorized"
import { paramResource, paramSubResource } from "../../middleware/resourceId"
@ -8,7 +8,7 @@ const {
} = require("@budibase/backend-core/permissions")
const { internalSearchValidator } = require("./utils/validators")
const router = Router()
const router = new Router()
router
/**

6
packages/server/src/api/routes/static.js → packages/server/src/api/routes/static.ts

@ -1,4 +1,4 @@
const Router = require("@koa/router")
import Router from "@koa/router"
import * as controller from "../controllers/static"
import { budibaseTempDir } from "../../utilities/budibaseDir"
import authorized from "../../middleware/authorized"
@ -10,10 +10,10 @@ import {
import * as env from "../../environment"
import { paramResource } from "../../middleware/resourceId"
const router = Router()
const router = new Router()
/* istanbul ignore next */
router.param("file", async (file, ctx, next) => {
router.param("file", async (file: any, ctx: any, next: any) => {
ctx.file = file && file.includes(".") ? file : "index.html"
if (!ctx.file.startsWith("budibase-client")) {
return next()

67
packages/server/src/api/routes/tests/utilities/TestFunctions.js → packages/server/src/api/routes/tests/utilities/TestFunctions.ts

@ -1,31 +1,38 @@
const rowController = require("../../../controllers/row")
const appController = require("../../../controllers/application")
const { AppStatus } = require("../../../../db/utils")
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const { TENANT_ID } = require("../../../../tests/utilities/structures")
const { getAppDB, doInAppContext } = require("@budibase/backend-core/context")
const env = require("../../../../environment")
function Request(appId, params) {
this.appId = appId
this.params = params
this.request = {}
import * as rowController from "../../../controllers/row"
import * as appController from "../../../controllers/application"
import { AppStatus } from "../../../../db/utils"
import { BUILTIN_ROLE_IDS } from "@budibase/backend-core/roles"
import { TENANT_ID } from "../../../../tests/utilities/structures"
import { getAppDB, doInAppContext } from "@budibase/backend-core/context"
import * as env from "../../../../environment"
class Request {
appId: any
params: any
request: any
body: any
constructor(appId: any, params: any) {
this.appId = appId
this.params = params
this.request = {}
}
}
function runRequest(appId, controlFunc, request) {
function runRequest(appId: any, controlFunc: any, request?: any) {
return doInAppContext(appId, async () => {
return controlFunc(request)
})
}
exports.getAllTableRows = async config => {
export const getAllTableRows = async (config: any) => {
const req = new Request(config.appId, { tableId: config.table._id })
await runRequest(config.appId, rowController.fetch, req)
return req.body
}
exports.clearAllApps = async (tenantId = TENANT_ID) => {
const req = { query: { status: AppStatus.DEV }, user: { tenantId } }
export const clearAllApps = async (tenantId = TENANT_ID) => {
const req: any = { query: { status: AppStatus.DEV }, user: { tenantId } }
await appController.fetch(req)
const apps = req.body
if (!apps || apps.length <= 0) {
@ -34,11 +41,11 @@ exports.clearAllApps = async (tenantId = TENANT_ID) => {
for (let app of apps) {
const { appId } = app
const req = new Request(null, { appId })
await runRequest(appId, appController.delete, req)
await runRequest(appId, appController.destroy, req)
}
}
exports.clearAllAutomations = async config => {
export const clearAllAutomations = async (config: any) => {
const automations = await config.getAllAutomations()
for (let auto of automations) {
await doInAppContext(config.appId, async () => {
@ -47,7 +54,12 @@ exports.clearAllAutomations = async config => {
}
}
exports.createRequest = (request, method, url, body) => {
export const createRequest = (
request: any,
method: any,
url: any,
body: any
) => {
let req
if (method === "POST") req = request.post(url).send(body)
@ -59,7 +71,12 @@ exports.createRequest = (request, method, url, body) => {
return req
}
exports.checkBuilderEndpoint = async ({ config, method, url, body }) => {
export const checkBuilderEndpoint = async ({
config,
method,
url,
body,
}: any) => {
const headers = await config.login({
userId: "us_fail",
builder: false,
@ -71,14 +88,14 @@ exports.checkBuilderEndpoint = async ({ config, method, url, body }) => {
.expect(403)
}
exports.checkPermissionsEndpoint = async ({
export const checkPermissionsEndpoint = async ({
config,
method,
url,
body,
passRole,
failRole,
}) => {
}: any) => {
const passHeader = await config.login({
roleId: passRole,
prodApp: true,
@ -106,11 +123,11 @@ exports.checkPermissionsEndpoint = async ({
.expect(403)
}
exports.getDB = () => {
export const getDB = () => {
return getAppDB()
}
exports.testAutomation = async (config, automation) => {
export const testAutomation = async (config: any, automation: any) => {
return runRequest(automation.appId, async () => {
return await config.request
.post(`/api/automations/${automation._id}/test`)
@ -126,7 +143,7 @@ exports.testAutomation = async (config, automation) => {
})
}
exports.runInProd = async func => {
export const runInProd = async (func: any) => {
const nodeEnv = env.NODE_ENV
const workerId = env.JEST_WORKER_ID
env._set("NODE_ENV", "PRODUCTION")

3
packages/server/src/automations/steps/createRow.ts

@ -1,4 +1,3 @@
import { quotas } from "@budibase/pro"
import { save } from "../../api/controllers/row"
import { cleanUpRow, getError } from "../automationUtils"
import { buildCtx } from "./utils"
@ -78,7 +77,7 @@ export async function run({ inputs, appId, emitter }: any) {
try {
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
await quotas.addRow(() => save(ctx))
await save(ctx)
return {
row: inputs.row,
response: ctx.body,

2
packages/server/src/automations/steps/deleteRow.ts

@ -1,7 +1,6 @@
import { destroy } from "../../api/controllers/row"
import { buildCtx } from "./utils"
import { getError } from "../automationUtils"
import { quotas } from "@budibase/pro"
export const definition = {
description: "Delete a row from your database",
@ -74,7 +73,6 @@ export async function run({ inputs, appId, emitter }: any) {
try {
await destroy(ctx)
await quotas.removeRow()
return {
response: ctx.body,
row: ctx.row,

1
packages/server/src/automations/tests/automation.spec.js

@ -1,4 +1,3 @@
jest.mock("../../utilities/usageQuota")
jest.mock("../../threads/automation")
jest.mock("../../utilities/redis", () => ({
init: jest.fn(),

19
packages/server/src/automations/tests/createRow.spec.js → packages/server/src/automations/tests/createRow.spec.ts

@ -1,10 +1,8 @@
jest.mock("../../utilities/usageQuota")
const usageQuota = require("../../utilities/usageQuota")
const setup = require("./utilities")
import * as setup from "./utilities"
describe("test the create row action", () => {
let table, row
let table: any
let row: any
let config = setup.getConfig()
beforeEach(async () => {
@ -36,20 +34,11 @@ describe("test the create row action", () => {
row: {
tableId: "invalid",
invalid: "invalid",
}
},
})
expect(res.success).toEqual(false)
})
it("check usage quota attempts", async () => {
await setup.runInProd(async () => {
await setup.runStep(setup.actions.CREATE_ROW.stepId, {
row
})
expect(usageQuota.update).toHaveBeenCalledWith("rows", 1)
})
})
it("should check invalid inputs return an error", async () => {
const res = await setup.runStep(setup.actions.CREATE_ROW.stepId, {})
expect(res.success).toEqual(false)

8
packages/server/src/automations/tests/deleteRow.spec.js → packages/server/src/automations/tests/deleteRow.spec.ts

@ -1,10 +1,9 @@
jest.mock("../../utilities/usageQuota")
const usageQuota = require("../../utilities/usageQuota")
const setup = require("./utilities")
describe("test the delete row action", () => {
let table, row, inputs
let table: any
let row: any
let inputs: any
let config = setup.getConfig()
beforeEach(async () => {
@ -37,7 +36,6 @@ describe("test the delete row action", () => {
it("check usage quota attempts", async () => {
await setup.runInProd(async () => {
await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs)
expect(usageQuota.update).toHaveBeenCalledWith("rows", -1)
})
})

2
packages/server/src/automations/tests/utilities/index.js

@ -18,7 +18,6 @@ exports.afterAll = () => {
exports.runInProd = async fn => {
env._set("NODE_ENV", "production")
env._set("USE_QUOTAS", 1)
let error
try {
await fn()
@ -26,7 +25,6 @@ exports.runInProd = async fn => {
error = err
}
env._set("NODE_ENV", "jest")
env._set("USE_QUOTAS", null)
if (error) {
throw error
}

2
packages/server/src/environment.js

@ -38,8 +38,6 @@ module.exports = {
ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS,
MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
USE_QUOTAS: process.env.USE_QUOTAS,
EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
REDIS_URL: process.env.REDIS_URL,
REDIS_PASSWORD: process.env.REDIS_PASSWORD,
INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,

32
packages/server/src/middleware/authorized.js → packages/server/src/middleware/authorized.ts

@ -1,8 +1,8 @@
const {
import {
getUserRoleHierarchy,
getRequiredResourceRole,
BUILTIN_ROLE_IDS,
} = require("@budibase/backend-core/roles")
} from "@budibase/backend-core/roles"
const {
PermissionTypes,
doesHaveBasePermission,
@ -12,7 +12,7 @@ const { isWebhookEndpoint } = require("./utils")
const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
const { getAppId } = require("@budibase/backend-core/context")
function hasResource(ctx) {
function hasResource(ctx: any) {
return ctx.resourceId != null
}
@ -24,7 +24,12 @@ const csrf = buildCsrfMiddleware()
* - Builders can access all resources.
* - Otherwise the user must have the required role.
*/
const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
const checkAuthorized = async (
ctx: any,
resourceRoles: any,
permType: any,
permLevel: any
) => {
// check if this is a builder api and the user is not a builder
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
const isBuilderApi = permType === PermissionTypes.BUILDER
@ -39,10 +44,10 @@ const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
}
const checkAuthorizedResource = async (
ctx,
resourceRoles,
permType,
permLevel
ctx: any,
resourceRoles: any,
permType: any,
permLevel: any
) => {
// get the user's roles
const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
@ -53,7 +58,9 @@ const checkAuthorizedResource = async (
// check if the user has the required role
if (resourceRoles.length > 0) {
// deny access if the user doesn't have the required resource role
const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
const found = userRoles.find(
(role: any) => resourceRoles.indexOf(role._id) !== -1
)
if (!found) {
ctx.throw(403, permError)
}
@ -63,9 +70,8 @@ const checkAuthorizedResource = async (
}
}
module.exports =
(permType, permLevel = null) =>
async (ctx, next) => {
export = (permType: any, permLevel: any = null) =>
async (ctx: any, next: any) => {
// webhooks don't need authentication, each webhook unique
// also internal requests (between services) don't need authorized
if (isWebhookEndpoint(ctx) || ctx.internal) {
@ -81,7 +87,7 @@ module.exports =
await builderMiddleware(ctx, permType)
// get the resource roles
let resourceRoles = []
let resourceRoles: any = []
const appId = getAppId()
if (appId && hasResource(ctx)) {
resourceRoles = await getRequiredResourceRole(permLevel, ctx)

134
packages/server/src/middleware/tests/usageQuota.spec.js

@ -1,134 +0,0 @@
jest.mock("../../db")
jest.mock("../../utilities/usageQuota")
jest.mock("@budibase/backend-core/tenancy", () => ({
getTenantId: () => "testing123"
}))
const usageQuotaMiddleware = require("../usageQuota")
const usageQuota = require("../../utilities/usageQuota")
const CouchDB = require("../../db")
const env = require("../../environment")
class TestConfiguration {
constructor() {
this.throw = jest.fn()
this.next = jest.fn()
this.middleware = usageQuotaMiddleware
this.ctx = {
throw: this.throw,
next: this.next,
appId: "test",
request: {
body: {}
},
req: {
method: "POST",
url: "/applications"
}
}
usageQuota.useQuotas = () => true
}
executeMiddleware() {
return this.middleware(this.ctx, this.next)
}
setProd(bool) {
if (bool) {
env.isDev = () => false
env.isProd = () => true
this.ctx.user = { tenantId: "test" }
} else {
env.isDev = () => true
env.isProd = () => false
}
}
setMethod(method) {
this.ctx.req.method = method
}
setUrl(url) {
this.ctx.req.url = url
}
setBody(body) {
this.ctx.request.body = body
}
setFiles(files) {
this.ctx.request.files = { file: files }
}
}
describe("usageQuota middleware", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
it("skips the middleware if there is no usage property or method", async () => {
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("passes through to next middleware if document already exists", async () => {
config.setProd(true)
config.setBody({
_id: "test",
_rev: "test",
})
CouchDB.mockImplementationOnce(() => ({
get: async () => true
}))
await config.executeMiddleware()
expect(config.next).toHaveBeenCalled()
})
it("throws if request has _id, but the document no longer exists", async () => {
config.setBody({
_id: "123",
_rev: "test",
})
config.setProd(true)
CouchDB.mockImplementationOnce(() => ({
get: async () => {
throw new Error()
}
}))
await config.executeMiddleware()
expect(config.throw).toHaveBeenCalledWith(404, `${config.ctx.request.body._id} does not exist`)
})
it("calculates and persists the correct usage quota for the relevant action", async () => {
config.setUrl("/rows")
await config.executeMiddleware()
expect(usageQuota.update).toHaveBeenCalledWith("rows", 1)
expect(config.next).toHaveBeenCalled()
})
// it("calculates the correct file size from a file upload call and adds it to quota", async () => {
// config.setUrl("/upload")
// config.setProd(true)
// config.setFiles([
// {
// size: 100
// },
// {
// size: 10000
// },
// ])
// await config.executeMiddleware()
// expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
// expect(config.next).toHaveBeenCalled()
// })
})

2
packages/server/src/migrations/functions/tests/quotas1.spec.js

@ -1,4 +1,3 @@
const env = require("../../../environment")
const TestConfig = require("../../../tests/utilities/TestConfiguration")
const syncApps = jest.fn()
@ -14,7 +13,6 @@ describe("run", () => {
beforeEach(async () => {
await config.init()
env._set("USE_QUOTAS", 1)
})
afterAll(config.end)

37
packages/server/src/migrations/functions/usageQuotas/tests/syncApps.spec.js

@ -1,37 +0,0 @@
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const TestConfig = require("../../../../tests/utilities/TestConfiguration")
const { getUsageQuotaDoc, update, Properties } = require("../../../../utilities/usageQuota")
const syncApps = require("../syncApps")
const env = require("../../../../environment")
describe("syncApps", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await config.init()
env._set("USE_QUOTAS", 1)
})
afterAll(config.end)
it("runs successfully", async () => {
// create the usage quota doc and mock usages
const db = getGlobalDB()
await getUsageQuotaDoc(db)
await update(Properties.APPS, 3)
let usageDoc = await getUsageQuotaDoc(db)
expect(usageDoc.usageQuota.apps).toEqual(3)
// create an extra app to test the migration
await config.createApp("quota-test")
// migrate
await syncApps.run()
// assert the migration worked
usageDoc = await getUsageQuotaDoc(db)
expect(usageDoc.usageQuota.apps).toEqual(2)
})
})

32
packages/server/src/migrations/functions/usageQuotas/tests/syncApps.spec.ts

@ -0,0 +1,32 @@
import TestConfig from "../../../../tests/utilities/TestConfiguration"
import * as syncApps from "../syncApps"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
describe("syncApps", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
it("runs successfully", async () => {
// create the usage quota doc and mock usages
await quotas.getQuotaUsage()
await quotas.setUsage(3, StaticQuotaName.APPS, QuotaUsageType.STATIC)
let usageDoc = await quotas.getQuotaUsage()
expect(usageDoc.usageQuota.apps).toEqual(3)
// create an extra app to test the migration
await config.createApp("quota-test")
// migrate
await syncApps.run()
// assert the migration worked
usageDoc = await quotas.getQuotaUsage()
expect(usageDoc.usageQuota.apps).toEqual(2)
})
})

43
packages/server/src/migrations/functions/usageQuotas/tests/syncRows.spec.js

@ -1,43 +0,0 @@
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const TestConfig = require("../../../../tests/utilities/TestConfiguration")
const { getUsageQuotaDoc, update, Properties } = require("../../../../utilities/usageQuota")
const syncRows = require("../syncRows")
const env = require("../../../../environment")
describe("syncRows", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await config.init()
env._set("USE_QUOTAS", 1)
})
afterAll(config.end)
it("runs successfully", async () => {
// create the usage quota doc and mock usages
const db = getGlobalDB()
await getUsageQuotaDoc(db)
await update(Properties.ROW, 300)
let usageDoc = await getUsageQuotaDoc(db)
expect(usageDoc.usageQuota.rows).toEqual(300)
// app 1
await config.createTable()
await config.createRow()
// app 2
await config.createApp("second-app")
await config.createTable()
await config.createRow()
await config.createRow()
// migrate
await syncRows.run()
// assert the migration worked
usageDoc = await getUsageQuotaDoc(db)
expect(usageDoc.usageQuota.rows).toEqual(3)
})
})

38
packages/server/src/migrations/functions/usageQuotas/tests/syncRows.spec.ts

@ -0,0 +1,38 @@
import TestConfig from "../../../../tests/utilities/TestConfiguration"
import * as syncRows from "../syncRows"
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
describe("syncRows", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await config.init()
})
afterAll(config.end)
it("runs successfully", async () => {
// create the usage quota doc and mock usages
await quotas.getQuotaUsage()
await quotas.setUsage(300, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
let usageDoc = await quotas.getQuotaUsage()
expect(usageDoc.usageQuota.rows).toEqual(300)
// app 1
await config.createTable()
await config.createRow()
// app 2
await config.createApp("second-app")
await config.createTable()
await config.createRow()
await config.createRow()
// migrate
await syncRows.run()
// assert the migration worked
usageDoc = await quotas.getQuotaUsage()
expect(usageDoc.usageQuota.rows).toEqual(3)
})
})

2
packages/server/src/module.d.ts

@ -3,3 +3,5 @@ declare module "@budibase/backend-core/tenancy"
declare module "@budibase/backend-core/db"
declare module "@budibase/backend-core/context"
declare module "@budibase/backend-core/cache"
declare module "@budibase/backend-core/permissions"
declare module "@budibase/backend-core/roles"

72
packages/server/src/utilities/tests/usageQuota/usageQuota.spec.js

@ -1,72 +0,0 @@
const getTenantId = jest.fn()
jest.mock("@budibase/backend-core/tenancy", () => ({
getTenantId
}))
const usageQuota = require("../../usageQuota")
const env = require("../../../environment")
class TestConfiguration {
constructor() {
this.enableQuotas()
}
enableQuotas = () => {
env.USE_QUOTAS = 1
}
disableQuotas = () => {
env.USE_QUOTAS = null
}
setTenantId = (tenantId) => {
getTenantId.mockReturnValue(tenantId)
}
setExcludedTenants = (tenants) => {
env.EXCLUDE_QUOTAS_TENANTS = tenants
}
reset = () => {
this.disableQuotas()
this.setExcludedTenants(null)
}
}
describe("usageQuota", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
afterEach(() => {
config.reset()
})
describe("useQuotas", () => {
it("works when no settings have been provided", () => {
config.reset()
expect(usageQuota.useQuotas()).toBe(false)
})
it("honours USE_QUOTAS setting", () => {
config.disableQuotas()
expect(usageQuota.useQuotas()).toBe(false)
config.enableQuotas()
expect(usageQuota.useQuotas()).toBe(true)
})
it("honours EXCLUDE_QUOTAS_TENANTS setting", () => {
config.setTenantId("test")
// tenantId is in the list
config.setExcludedTenants("test, test2, test2")
expect(usageQuota.useQuotas()).toBe(false)
config.setExcludedTenants("test,test2,test2")
expect(usageQuota.useQuotas()).toBe(false)
// tenantId is not in the list
config.setTenantId("other")
expect(usageQuota.useQuotas()).toBe(true)
})
})
})

10
packages/server/tsconfig.build.json

@ -0,0 +1,10 @@
{
// Used for building with tsc
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"**/*.json",
"**/*.spec.js",
"**/*.spec.ts"
]
}

4
packages/server/tsconfig.json

@ -19,7 +19,7 @@
"exclude": [
"node_modules",
"**/*.json",
"**/*.spec.ts",
"**/*.spec.js"
"**/*.spec.js",
// "**/*.spec.ts" // don't exclude spec.ts files for editor support
]
}

27
packages/server/yarn.lock

@ -2531,13 +2531,13 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@^26.0.23":
version "26.0.24"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a"
integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==
"@types/jest@^27.4.1":
version "27.4.1"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d"
integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw==
dependencies:
jest-diff "^26.0.0"
pretty-format "^26.0.0"
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
"@types/json-schema@*", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8":
version "7.0.9"
@ -2577,6 +2577,13 @@
"@types/koa-compose" "*"
"@types/node" "*"
"@types/koa__router@^8.0.11":
version "8.0.11"
resolved "https://registry.yarnpkg.com/@types/koa__router/-/koa__router-8.0.11.tgz#d7b37e6db934fc072ea1baa2ab92bc8ac4564f3e"
integrity sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ==
dependencies:
"@types/koa" "*"
"@types/lodash@^4.14.179":
version "4.14.180"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
@ -7375,7 +7382,7 @@ jest-diff@^24.9.0:
jest-get-type "^24.9.0"
pretty-format "^24.9.0"
jest-diff@^26.0.0, jest-diff@^26.6.2:
jest-diff@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394"
integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==
@ -7614,7 +7621,7 @@ jest-matcher-utils@^26.6.2:
jest-get-type "^26.3.0"
pretty-format "^26.6.2"
jest-matcher-utils@^27.5.1:
jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
@ -10413,7 +10420,7 @@ pretty-format@^24.9.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
pretty-format@^26.0.0, pretty-format@^26.6.2:
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
@ -10423,7 +10430,7 @@ pretty-format@^26.0.0, pretty-format@^26.6.2:
ansi-styles "^4.0.0"
react-is "^17.0.1"
pretty-format@^27.5.1:
pretty-format@^27.0.0, pretty-format@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==

3
packages/worker/src/api/routes/tests/auth.spec.js

@ -75,7 +75,8 @@ describe("/api/global/auth", () => {
afterEach(() => {
expect(strategyFactory).toBeCalledWith(
chosenConfig,
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`
`http://localhost:10000/api/global/auth/${TENANT_ID}/oidc/callback`,
expect.any(Function)
)
})

1196
packages/worker/yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save