forked from tsai/budibase
43 changed files with 295 additions and 5110 deletions
@ -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", |
||||
|
} |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -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()
|
|
||||
// })
|
|
||||
}) |
|
||||
@ -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) |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
@ -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) |
||||
|
}) |
||||
|
}) |
||||
@ -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) |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
@ -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) |
||||
|
}) |
||||
|
}) |
||||
@ -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) |
|
||||
}) |
|
||||
}) |
|
||||
}) |
|
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
// Used for building with tsc |
||||
|
"extends": "./tsconfig.json", |
||||
|
"exclude": [ |
||||
|
"node_modules", |
||||
|
"**/*.json", |
||||
|
"**/*.spec.js", |
||||
|
"**/*.spec.ts" |
||||
|
] |
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue