diff --git a/.vscode/settings.json b/.vscode/settings.json index 71f0092a59..d48458fbd7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,6 @@ ] }, "[typescript]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, } diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index 889d7e9e23..dd75b2daa3 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -76,7 +76,7 @@ affinity: {} globals: appVersion: "latest" budibaseEnv: PRODUCTION - tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" + tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" enableAnalytics: "1" sentryDSN: "" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" diff --git a/hosting/single/runner.sh b/hosting/single/runner.sh index c52f699077..0bd377cd7f 100644 --- a/hosting/single/runner.sh +++ b/hosting/single/runner.sh @@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME [[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000 [[ -z "${NODE_ENV}" ]] && export NODE_ENV=production [[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU -[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS" +[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR" [[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app [[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379 [[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1 diff --git a/lerna.json b/lerna.json index 5ae449aa0e..30afd475d4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index a3452c0b22..96f28879d4 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -23,7 +23,7 @@ }, "dependencies": { "@budibase/nano": "10.1.1", - "@budibase/types": "2.2.12-alpha.64", + "@budibase/types": "2.2.12-alpha.68", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/backend-core/src/featureFlags/index.ts b/packages/backend-core/src/featureFlags/index.ts index 71e226c976..34ee3599a5 100644 --- a/packages/backend-core/src/featureFlags/index.ts +++ b/packages/backend-core/src/featureFlags/index.ts @@ -6,7 +6,7 @@ import * as tenancy from "../tenancy" * The env var is formatted as: * tenant1:feature1:feature2,tenant2:feature1 */ -function getFeatureFlags() { +export function buildFeatureFlags() { if (!env.TENANT_FEATURE_FLAGS) { return } @@ -27,8 +27,6 @@ function getFeatureFlags() { return tenantFeatureFlags } -const TENANT_FEATURE_FLAGS = getFeatureFlags() - export function isEnabled(featureFlag: string) { const tenantId = tenancy.getTenantId() const flags = getTenantFeatureFlags(tenantId) @@ -36,18 +34,36 @@ export function isEnabled(featureFlag: string) { } export function getTenantFeatureFlags(tenantId: string) { - const flags = [] + let flags: string[] = [] + const envFlags = buildFeatureFlags() + if (envFlags) { + const globalFlags = envFlags["*"] + const tenantFlags = envFlags[tenantId] || [] - if (TENANT_FEATURE_FLAGS) { - const globalFlags = TENANT_FEATURE_FLAGS["*"] - const tenantFlags = TENANT_FEATURE_FLAGS[tenantId] + // Explicitly exclude tenants from global features if required. + // Prefix the tenant flag with '!' + const tenantOverrides = tenantFlags.reduce( + (acc: string[], flag: string) => { + if (flag.startsWith("!")) { + let stripped = flag.substring(1) + acc.push(stripped) + } + return acc + }, + [] + ) if (globalFlags) { flags.push(...globalFlags) } - if (tenantFlags) { + if (tenantFlags.length) { flags.push(...tenantFlags) } + + // Purge any tenant specific overrides + flags = flags.filter(flag => { + return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!") + }) } return flags @@ -57,4 +73,5 @@ export enum TenantFeatureFlag { LICENSING = "LICENSING", GOOGLE_SHEETS = "GOOGLE_SHEETS", USER_GROUPS = "USER_GROUPS", + ONBOARDING_TOUR = "ONBOARDING_TOUR", } diff --git a/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts b/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts new file mode 100644 index 0000000000..1b68959329 --- /dev/null +++ b/packages/backend-core/src/featureFlags/tests/featureFlags.spec.ts @@ -0,0 +1,85 @@ +import { + TenantFeatureFlag, + buildFeatureFlags, + getTenantFeatureFlags, +} from "../" +import env from "../../environment" + +const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag + +describe("featureFlags", () => { + beforeEach(() => { + env._set("TENANT_FEATURE_FLAGS", "") + }) + + it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => { + let features = buildFeatureFlags() + expect(features).toBeUndefined() + }) + + it("Should generate a map of global and named tenant feature flags from the env value", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}` + ) + + const parsedFlags: Record = { + "*": [ONBOARDING_TOUR], + tenant1: [`!${ONBOARDING_TOUR}`, LICENSING], + tenant2: [USER_GROUPS], + } + + let features = buildFeatureFlags() + + expect(features).toBeDefined() + expect(features).toEqual(parsedFlags) + }) + + it("Should add feature flag flag only to explicitly configured tenant", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS]) + }) +}) + +it("Should exclude tenant1 from global feature flag", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([LICENSING]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR]) +}) + +it("Should explicitly add flags to configured tenants only", async () => { + env._set( + "TENANT_FEATURE_FLAGS", + `tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}` + ) + + let tenant1Flags = getTenantFeatureFlags("tenant1") + let tenant2Flags = getTenantFeatureFlags("tenant2") + + expect(tenant1Flags).toBeDefined() + expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING]) + + expect(tenant2Flags).toBeDefined() + expect(tenant2Flags).toEqual([LICENSING]) +}) diff --git a/packages/bbui/package.json b/packages/bbui/package.json index dbafc7e8f9..2864a8ad1e 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,7 +38,7 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/string-templates": "2.2.12-alpha.64", + "@budibase/string-templates": "2.2.12-alpha.68", "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", diff --git a/packages/builder/package.json b/packages/builder/package.json index fe722e9032..3164654702 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "license": "GPL-3.0", "private": true, "scripts": { @@ -58,10 +58,10 @@ } }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.64", - "@budibase/client": "2.2.12-alpha.64", - "@budibase/frontend-core": "2.2.12-alpha.64", - "@budibase/string-templates": "2.2.12-alpha.64", + "@budibase/bbui": "2.2.12-alpha.68", + "@budibase/client": "2.2.12-alpha.68", + "@budibase/frontend-core": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.68", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte index 211a6512a2..53d50d57a3 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/IntegrationConfigForm.svelte @@ -177,7 +177,7 @@ showModal(configKey)} variables={$environment.variables} - type={schema[configKey].type} + type={configKey === "port" ? "string" : schema[configKey].type} on:change bind:value={config[configKey]} error={$validation.errors[configKey]} diff --git a/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte b/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte index c929f1039e..9a05b90567 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte @@ -22,6 +22,7 @@ const dispatch = createEventDispatcher() let bindingDrawer + let valid = true $: readableValue = runtimeToReadableBinding(bindings, value) $: tempValue = readableValue @@ -76,12 +77,15 @@ Add the objects on the left to enrich your text. - + (tempValue = event.detail)} {bindings} {allowJS} diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte index 43e3b0ed99..958a6d233d 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/ButtonActionDrawer.svelte @@ -118,6 +118,10 @@ const getAllBindings = (bindings, eventContextBindings, actions) => { let allBindings = eventContextBindings.concat(bindings) + if (!actions) { + return [] + } + // Ensure bindings are generated for all "update state" action keys actions .filter(action => { diff --git a/packages/builder/src/helpers/featureFlags.js b/packages/builder/src/helpers/featureFlags.js index ae6646bd9f..462dae8c54 100644 --- a/packages/builder/src/helpers/featureFlags.js +++ b/packages/builder/src/helpers/featureFlags.js @@ -4,6 +4,7 @@ import { get } from "svelte/store" export const TENANT_FEATURE_FLAGS = { LICENSING: "LICENSING", USER_GROUPS: "USER_GROUPS", + ONBOARDING_TOUR: "ONBOARDING_TOUR", } export const isEnabled = featureFlag => { diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index c99776320f..f561bd8ecd 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -2,6 +2,7 @@ import { store, automationStore } from "builderStore" import { roles, flags } from "stores/backend" import { auth } from "stores/portal" + import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags" import { ActionMenu, MenuItem, @@ -68,7 +69,10 @@ } const initTour = async () => { - if (!$auth.user?.onboardedAt) { + if ( + !$auth.user?.onboardedAt && + isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR) + ) { // Determine the correct step const activeNav = $layout.children.find(c => $isActive(c.path)) const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING] diff --git a/packages/cli/package.json b/packages/cli/package.json index 0d884ee324..4aa0d5fab7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { @@ -26,9 +26,9 @@ "outputPath": "build" }, "dependencies": { - "@budibase/backend-core": "2.2.12-alpha.64", - "@budibase/string-templates": "2.2.12-alpha.64", - "@budibase/types": "2.2.12-alpha.64", + "@budibase/backend-core": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/types": "2.2.12-alpha.68", "axios": "0.21.2", "chalk": "4.1.0", "cli-progress": "3.11.2", diff --git a/packages/client/package.json b/packages/client/package.json index 1c2f54b426..423fa18357 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -19,9 +19,9 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/bbui": "2.2.12-alpha.64", - "@budibase/frontend-core": "2.2.12-alpha.64", - "@budibase/string-templates": "2.2.12-alpha.64", + "@budibase/bbui": "2.2.12-alpha.68", + "@budibase/frontend-core": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.68", "@spectrum-css/button": "^3.0.3", "@spectrum-css/card": "^3.0.3", "@spectrum-css/divider": "^1.0.3", diff --git a/packages/frontend-core/package.json b/packages/frontend-core/package.json index dae6f5324b..be62fae760 100644 --- a/packages/frontend-core/package.json +++ b/packages/frontend-core/package.json @@ -1,12 +1,12 @@ { "name": "@budibase/frontend-core", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase frontend core libraries used in builder and client", "author": "Budibase", "license": "MPL-2.0", "svelte": "src/index.js", "dependencies": { - "@budibase/bbui": "2.2.12-alpha.64", + "@budibase/bbui": "2.2.12-alpha.68", "lodash": "^4.17.21", "svelte": "^3.46.2" } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 865c3a755f..6b015d6ea2 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/sdk", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase Public API SDK", "author": "Budibase", "license": "MPL-2.0", diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index b55bde7906..d5d516d999 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -12,7 +12,7 @@ ENV COUCH_DB_URL=https://couchdb.budi.live:5984 ENV BUDIBASE_ENVIRONMENT=PRODUCTION ENV SERVICE=app-service ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU -ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS +ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR ENV ACCOUNT_PORTAL_URL=https://account.budibase.app # copy files and install dependencies diff --git a/packages/server/package.json b/packages/server/package.json index 923d004b94..570044d56c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase Web Server", "main": "src/index.ts", "repository": { @@ -43,11 +43,11 @@ "license": "GPL-3.0", "dependencies": { "@apidevtools/swagger-parser": "10.0.3", - "@budibase/backend-core": "2.2.12-alpha.64", - "@budibase/client": "2.2.12-alpha.64", - "@budibase/pro": "2.2.12-alpha.64", - "@budibase/string-templates": "2.2.12-alpha.64", - "@budibase/types": "2.2.12-alpha.64", + "@budibase/backend-core": "2.2.12-alpha.68", + "@budibase/client": "2.2.12-alpha.68", + "@budibase/pro": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/types": "2.2.12-alpha.68", "@bull-board/api": "3.7.0", "@bull-board/koa": "3.9.4", "@elastic/elasticsearch": "7.10.0", diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js index b8566bbf4c..543e00ac59 100644 --- a/packages/server/scripts/dev/manage.js +++ b/packages/server/scripts/dev/manage.js @@ -36,7 +36,7 @@ async function init() { COUCH_DB_PASSWORD: "budibase", COUCH_DB_USER: "budibase", SELF_HOSTED: 1, - DISABLE_ACCOUNT_PORTAL: "", + DISABLE_ACCOUNT_PORTAL: 1, MULTI_TENANCY: "", DISABLE_THREADING: 1, SERVICE: "app-service", @@ -44,7 +44,7 @@ async function init() { BB_ADMIN_USER_EMAIL: "", BB_ADMIN_USER_PASSWORD: "", PLUGINS_DIR: "", - TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS", + TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 19eb1ddf34..52cfea9ce4 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1278,13 +1278,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.64.tgz#5000c09344cafb5a993b497f1bf270489b617816" - integrity sha512-KPpUi8xZKHRlVU5H7lznXDNk/XZtUPhSdIAxFYoemvR3FUGZW4TRxEa07ZU5y6NssiDE/Fy/pCdPYO4fNJQncQ== +"@budibase/backend-core@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.68.tgz#9efda78dfa35d3c431da188b4e3a0011b734c6b2" + integrity sha512-k+Edcvz3XcddlJv9YR+TuoYs7e583lpf9nRCu6WOO889s9e8QS+zzfkC9++Vx8aH16JTizibPDY9oNeRrMQALw== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.64" + "@budibase/types" "2.2.12-alpha.68" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -1379,13 +1379,13 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.64.tgz#0fd36cf9cbf360282cef4022780217b622854895" - integrity sha512-3fMLsgGTqrqa3WNYk5X4vNAoyLW+WGdPvyyRhJx+O5XjG6EIEzx1XlabmpYDcFbz7np0QiLUxW68yV6uggwVfg== +"@budibase/pro@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.68.tgz#ee992a6451ecaabdb71b5c4d5f9a269710524529" + integrity sha512-jp+gYg03Q39kc9PIEREC/3QikTzW9mavGrpnWQNcaFyELwmmRbI5tDZkxRmK38TNuW/1ArqKricd9uCVRb3UGA== dependencies: - "@budibase/backend-core" "2.2.12-alpha.64" - "@budibase/types" "2.2.12-alpha.64" + "@budibase/backend-core" "2.2.12-alpha.68" + "@budibase/types" "2.2.12-alpha.68" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" @@ -1411,10 +1411,10 @@ svelte-apexcharts "^1.0.2" svelte-flatpickr "^3.1.0" -"@budibase/types@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.64.tgz#329ec9355c3c94cf98f382157c73473dd75a46b7" - integrity sha512-G2BxiP8WyOE2l+feKpF13JiFgPbyHUYPgfW0WycIcUsN9XetH3GqpiB0SRWxq80i41FpzaC2aUArGXIC+2Ai5Q== +"@budibase/types@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.68.tgz#6284f083aceca49a8de52ebc15c9da8c8416586e" + integrity sha512-xNl/L6M8X+qcVytBgdPSWNM7CYk7Rr2I+ubx5+3u4Z8tF3IWoqk6pj7hMMCORAYAGK7ZdjG7xx+tvXiKK8v1NQ== "@bull-board/api@3.7.0": version "3.7.0" diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 5a69f51432..a35efd87c5 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/types/package.json b/packages/types/package.json index 74aed42dd8..7a89209e80 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/types", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase types", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/worker/Dockerfile b/packages/worker/Dockerfile index 046b844815..e0cac94eda 100644 --- a/packages/worker/Dockerfile +++ b/packages/worker/Dockerfile @@ -23,7 +23,7 @@ ENV NODE_ENV=production ENV CLUSTER_MODE=${CLUSTER_MODE} ENV SERVICE=worker-service ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU -ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS +ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR ENV ACCOUNT_PORTAL_URL=https://account.budibase.app CMD ["./docker_run.sh"] diff --git a/packages/worker/package.json b/packages/worker/package.json index b5c95a65a7..9771604c01 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "2.2.12-alpha.64", + "version": "2.2.12-alpha.68", "description": "Budibase background service", "main": "src/index.ts", "repository": { @@ -36,10 +36,10 @@ "author": "Budibase", "license": "GPL-3.0", "dependencies": { - "@budibase/backend-core": "2.2.12-alpha.64", - "@budibase/pro": "2.2.12-alpha.64", - "@budibase/string-templates": "2.2.12-alpha.64", - "@budibase/types": "2.2.12-alpha.64", + "@budibase/backend-core": "2.2.12-alpha.68", + "@budibase/pro": "2.2.12-alpha.68", + "@budibase/string-templates": "2.2.12-alpha.68", + "@budibase/types": "2.2.12-alpha.68", "@koa/router": "8.0.8", "@sentry/node": "6.17.7", "@techpass/passport-openidconnect": "0.3.2", diff --git a/packages/worker/scripts/dev/manage.js b/packages/worker/scripts/dev/manage.js index a4eaf37162..01cdef08ff 100644 --- a/packages/worker/scripts/dev/manage.js +++ b/packages/worker/scripts/dev/manage.js @@ -21,14 +21,14 @@ async function init() { COUCH_DB_PASSWORD: "budibase", // empty string is false MULTI_TENANCY: "", - DISABLE_ACCOUNT_PORTAL: "", + DISABLE_ACCOUNT_PORTAL: 1, ACCOUNT_PORTAL_URL: "http://localhost:10001", ACCOUNT_PORTAL_API_KEY: "budibase", PLATFORM_URL: "http://localhost:10000", APPS_URL: "http://localhost:4001", SERVICE: "worker-service", DEPLOYMENT_ENVIRONMENT: "development", - TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS", + TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR", } let envFile = "" Object.keys(envFileJson).forEach(key => { diff --git a/packages/worker/src/api/controllers/global/configs.ts b/packages/worker/src/api/controllers/global/configs.ts index 89c67180bc..855d766a87 100644 --- a/packages/worker/src/api/controllers/global/configs.ts +++ b/packages/worker/src/api/controllers/global/configs.ts @@ -267,7 +267,7 @@ export async function publicSettings(ctx: Ctx) { // enrich the logo url // empty url means deleted - if (config.config.logoUrl !== "") { + if (config.config.logoUrl && config.config.logoUrl !== "") { config.config.logoUrl = objectStore.getGlobalFileUrl( "settings", "logoUrl", diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock index 268317a327..da216f1b0f 100644 --- a/packages/worker/yarn.lock +++ b/packages/worker/yarn.lock @@ -475,13 +475,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/backend-core@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.64.tgz#5000c09344cafb5a993b497f1bf270489b617816" - integrity sha512-KPpUi8xZKHRlVU5H7lznXDNk/XZtUPhSdIAxFYoemvR3FUGZW4TRxEa07ZU5y6NssiDE/Fy/pCdPYO4fNJQncQ== +"@budibase/backend-core@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.2.12-alpha.68.tgz#9efda78dfa35d3c431da188b4e3a0011b734c6b2" + integrity sha512-k+Edcvz3XcddlJv9YR+TuoYs7e583lpf9nRCu6WOO889s9e8QS+zzfkC9++Vx8aH16JTizibPDY9oNeRrMQALw== dependencies: "@budibase/nano" "10.1.1" - "@budibase/types" "2.2.12-alpha.64" + "@budibase/types" "2.2.12-alpha.68" "@shopify/jest-koa-mocks" "5.0.1" "@techpass/passport-openidconnect" "0.3.2" aws-cloudfront-sign "2.2.0" @@ -526,13 +526,13 @@ qs "^6.11.0" tough-cookie "^4.1.2" -"@budibase/pro@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.64.tgz#0fd36cf9cbf360282cef4022780217b622854895" - integrity sha512-3fMLsgGTqrqa3WNYk5X4vNAoyLW+WGdPvyyRhJx+O5XjG6EIEzx1XlabmpYDcFbz7np0QiLUxW68yV6uggwVfg== +"@budibase/pro@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.2.12-alpha.68.tgz#ee992a6451ecaabdb71b5c4d5f9a269710524529" + integrity sha512-jp+gYg03Q39kc9PIEREC/3QikTzW9mavGrpnWQNcaFyELwmmRbI5tDZkxRmK38TNuW/1ArqKricd9uCVRb3UGA== dependencies: - "@budibase/backend-core" "2.2.12-alpha.64" - "@budibase/types" "2.2.12-alpha.64" + "@budibase/backend-core" "2.2.12-alpha.68" + "@budibase/types" "2.2.12-alpha.68" "@koa/router" "8.0.8" bull "4.10.1" joi "17.6.0" @@ -540,10 +540,10 @@ lru-cache "^7.14.1" node-fetch "^2.6.1" -"@budibase/types@2.2.12-alpha.64": - version "2.2.12-alpha.64" - resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.64.tgz#329ec9355c3c94cf98f382157c73473dd75a46b7" - integrity sha512-G2BxiP8WyOE2l+feKpF13JiFgPbyHUYPgfW0WycIcUsN9XetH3GqpiB0SRWxq80i41FpzaC2aUArGXIC+2Ai5Q== +"@budibase/types@2.2.12-alpha.68": + version "2.2.12-alpha.68" + resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.2.12-alpha.68.tgz#6284f083aceca49a8de52ebc15c9da8c8416586e" + integrity sha512-xNl/L6M8X+qcVytBgdPSWNM7CYk7Rr2I+ubx5+3u4Z8tF3IWoqk6pj7hMMCORAYAGK7ZdjG7xx+tvXiKK8v1NQ== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" diff --git a/qa-core/.env b/qa-core/.env index 93b5fde74a..096fb4e157 100644 --- a/qa-core/.env +++ b/qa-core/.env @@ -4,4 +4,7 @@ ENCRYPTED_TEST_PUBLIC_API_KEY=a65722f06bee5caeadc5d7ca2f543a43-d610e627344210c64 COUCH_DB_URL=http://budibase:budibase@localhost:4567 COUCH_DB_USER=budibase COUCH_DB_PASSWORD=budibase -JWT_SECRET=test \ No newline at end of file +JWT_SECRET=test +BUDIBASE_SERVER_URL=http://localhost:4100 +BUDIBASE_HOST= budirelease.live +BUDIBASE_ACCOUNTS_URL=https://account.budirelease.live \ No newline at end of file diff --git a/qa-core/scripts/jestSetup.js b/qa-core/scripts/jestSetup.js index 77565783c3..cd63258f7a 100644 --- a/qa-core/scripts/jestSetup.js +++ b/qa-core/scripts/jestSetup.js @@ -16,4 +16,4 @@ if (!process.env.DEBUG) { global.console.log = jest.fn() // console.log are ignored in tests } -jest.setTimeout(10000) +jest.setTimeout(60000) diff --git a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts index dafc2b1ff2..ef47d8a12b 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/InternalAPIClient.ts @@ -11,17 +11,22 @@ interface ApiOptions { class InternalAPIClient { host: string + tenantName?: string appId?: string cookie?: string constructor(appId?: string) { - if (!env.BUDIBASE_SERVER_URL) { + if (!env.BUDIBASE_HOST) { throw new Error("Must set BUDIBASE_SERVER_URL env var") } - this.host = `${env.BUDIBASE_SERVER_URL}/api` + this.host = `${env.BUDIBASE_HOST}/api` this.appId = appId } + setTenantName(tenantName: string) { + this.tenantName = tenantName + } + apiCall = (method: APIMethod) => async (url = "", options: ApiOptions = {}) => { @@ -33,15 +38,25 @@ class InternalAPIClient { "Content-Type": "application/json", Accept: "application/json", cookie: this.cookie, + redirect: "follow", + follow: 20, ...options.headers, }, credentials: "include", } + // prettier-ignore // @ts-ignore - const response = await fetch(`${this.host}${url}`, requestOptions) - if (response.status !== 200) { + const response = await fetch(`https://${process.env.TENANT_ID}.${this.host}${url}`, requestOptions) + + if (response.status == 404 || response.status == 500) { + console.error("Error in apiCall") + console.error("Response:") console.error(response) + console.error("Response body:") + console.error(response.body) + console.error("Request body:") + console.error(requestOptions.body) } return response } diff --git a/qa-core/src/config/internal-api/TestConfiguration/accounts.ts b/qa-core/src/config/internal-api/TestConfiguration/accounts.ts new file mode 100644 index 0000000000..fdf5aedbd0 --- /dev/null +++ b/qa-core/src/config/internal-api/TestConfiguration/accounts.ts @@ -0,0 +1,38 @@ +import { Response } from "node-fetch" +import { Account } from "@budibase/types" +import AccountsAPIClient from "./accountsAPIClient" +import { NewAccount } from "../fixtures/types/newAccount" + +export default class AccountsApi { + api: AccountsAPIClient + + constructor(AccountsAPIClient: AccountsAPIClient) { + this.api = AccountsAPIClient + } + + async validateEmail(email: string): Promise { + const response = await this.api.post(`/accounts/validate/email`, { + body: { email }, + }) + expect(response).toHaveStatusCode(200) + return response + } + + async validateTenantId(tenantId: string): Promise { + const response = await this.api.post(`/accounts/validate/tenantId`, { + body: { tenantId }, + }) + expect(response).toHaveStatusCode(200) + return response + } + + async create(body: Partial): Promise<[Response, Account]> { + const headers = { + "no-verify": "1", + } + const response = await this.api.post(`/accounts`, { body, headers }) + const json = await response.json() + expect(response).toHaveStatusCode(201) + return [response, json] + } +} diff --git a/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts new file mode 100644 index 0000000000..aff821a7ac --- /dev/null +++ b/qa-core/src/config/internal-api/TestConfiguration/accountsAPIClient.ts @@ -0,0 +1,64 @@ +import env from "../../../environment" +import fetch, { HeadersInit } from "node-fetch" + +type APIMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" + +interface ApiOptions { + method?: APIMethod + body?: object + headers?: HeadersInit | undefined +} + +class AccountsAPIClient { + host: string + appId?: string + cookie?: string + + constructor(appId?: string) { + if (!env.BUDIBASE_ACCOUNTS_URL) { + throw new Error("Must set BUDIBASE_SERVER_URL env var") + } + this.host = `${env.BUDIBASE_ACCOUNTS_URL}/api` + this.appId = appId + } + + apiCall = + (method: APIMethod) => + async (url = "", options: ApiOptions = {}) => { + const requestOptions = { + method, + body: JSON.stringify(options.body), + headers: { + "x-budibase-app-id": this.appId, + "Content-Type": "application/json", + Accept: "application/json", + cookie: this.cookie, + redirect: "follow", + follow: 20, + ...options.headers, + }, + credentials: "include", + } + + // @ts-ignore + const response = await fetch(`${this.host}${url}`, requestOptions) + if (response.status == 404 || response.status == 500) { + console.error("Error in apiCall") + console.error("Response:") + console.error(response) + console.error("Response body:") + console.error(response.body) + console.error("Request body:") + console.error(requestOptions.body) + } + return response + } + + post = this.apiCall("POST") + get = this.apiCall("GET") + patch = this.apiCall("PATCH") + del = this.apiCall("DELETE") + put = this.apiCall("PUT") +} + +export default AccountsAPIClient diff --git a/qa-core/src/config/internal-api/TestConfiguration/applications.ts b/qa-core/src/config/internal-api/TestConfiguration/applications.ts index aac82df013..9c1eec7663 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/applications.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/applications.ts @@ -110,11 +110,10 @@ export default class AppApi { return [response, json] } - async delete(appId: string): Promise<[Response, any]> { + async delete(appId: string): Promise { const response = await this.api.del(`/applications/${appId}`) - const json = await response.json() - expect(response).toHaveStatusCode(200) - return [response, json] + expect(response).toHaveStatusCode(204) + return response } async rename( diff --git a/qa-core/src/config/internal-api/TestConfiguration/index.ts b/qa-core/src/config/internal-api/TestConfiguration/index.ts index 08ca0d3cf3..c72f48690a 100644 --- a/qa-core/src/config/internal-api/TestConfiguration/index.ts +++ b/qa-core/src/config/internal-api/TestConfiguration/index.ts @@ -1,10 +1,13 @@ import ApplicationApi from "./applications" import AuthApi from "./auth" import InternalAPIClient from "./InternalAPIClient" +import AccountsApiClient from "./accountsAPIClient" import TablesApi from "./tables" import RowApi from "./rows" import ScreenApi from "./screens" import UserManagementApi from "./userManagement" +import AccountsApi from "./accounts" +import { generateAccount } from "../fixtures/accounts" export default class TestConfiguration { applications: ApplicationApi @@ -14,14 +17,24 @@ export default class TestConfiguration { tables: TablesApi rows: RowApi users: UserManagementApi + accounts: AccountsApi + apiClient: InternalAPIClient + accountsApiClient: AccountsApiClient - constructor(apiClient: InternalAPIClient) { - this.applications = new ApplicationApi(apiClient) - this.tables = new TablesApi(apiClient) - this.rows = new RowApi(apiClient) - this.auth = new AuthApi(apiClient) - this.screen = new ScreenApi(apiClient) - this.users = new UserManagementApi(apiClient) + constructor( + apiClient: InternalAPIClient, + accountsApiClient: AccountsApiClient + ) { + this.apiClient = apiClient + this.accountsApiClient = accountsApiClient + + this.applications = new ApplicationApi(this.apiClient) + this.tables = new TablesApi(this.apiClient) + this.rows = new RowApi(this.apiClient) + this.auth = new AuthApi(this.apiClient) + this.screen = new ScreenApi(this.apiClient) + this.users = new UserManagementApi(this.apiClient) + this.accounts = new AccountsApi(this.accountsApiClient) this.context = {} } @@ -31,8 +44,31 @@ export default class TestConfiguration { process.env.BB_ADMIN_USER_PASSWORD ) } + // TODO: add logic to setup or login based in env variables + + async setupAccountAndTenant() { + const account = generateAccount() + await this.accounts.validateEmail(account.email) + await this.accounts.validateTenantId(account.tenantId) + process.env.TENANT_ID = account.tenantId + await this.accounts.create(account) + await this.updateApiClients(account.tenantName) + await this.auth.login(account.email, account.password) + } + + async updateApiClients(tenantName: string) { + this.apiClient.setTenantName(tenantName) + this.applications = new ApplicationApi(this.apiClient) + this.tables = new TablesApi(this.apiClient) + this.rows = new RowApi(this.apiClient) + this.auth = new AuthApi(this.apiClient) + this.screen = new ScreenApi(this.apiClient) + this.users = new UserManagementApi(this.apiClient) + this.context = {} + } async login(email: string, password: string) { + await this.auth.logout() await this.auth.login(email, password) } diff --git a/qa-core/src/config/internal-api/fixtures/accounts.ts b/qa-core/src/config/internal-api/fixtures/accounts.ts new file mode 100644 index 0000000000..dbeabae928 --- /dev/null +++ b/qa-core/src/config/internal-api/fixtures/accounts.ts @@ -0,0 +1,21 @@ +import { NewAccount } from "./types/newAccount" + +import generator from "../../generator" +import { Hosting } from "@budibase/types" + +export const generateAccount = (): Partial => { + const randomGuid = generator.guid() + let tenant: string = "a" + randomGuid + tenant = tenant.replace(/-/g, "") + + return { + email: `qa+${randomGuid}@budibase.com`, + hosting: Hosting.CLOUD, + name: `qa+${randomGuid}@budibase.com`, + password: `${randomGuid}`, + profession: "software_engineer", + size: "10+", + tenantId: `${tenant}`, + tenantName: `${tenant}`, + } +} diff --git a/qa-core/src/config/internal-api/fixtures/types/newAccount.ts b/qa-core/src/config/internal-api/fixtures/types/newAccount.ts new file mode 100644 index 0000000000..e7ad88e697 --- /dev/null +++ b/qa-core/src/config/internal-api/fixtures/types/newAccount.ts @@ -0,0 +1,5 @@ +import { Account } from "@budibase/types" + +export interface NewAccount extends Account { + password: string +} diff --git a/qa-core/src/environment.ts b/qa-core/src/environment.ts index b0ed3cec85..e8119c3918 100644 --- a/qa-core/src/environment.ts +++ b/qa-core/src/environment.ts @@ -1,6 +1,9 @@ const env = { BUDIBASE_SERVER_URL: process.env.BUDIBASE_SERVER_URL, + BUDIBASE_ACCOUNT_URL: process.env.BUDIBASE_ACCOUNT_URL, BUDIBASE_PUBLIC_API_KEY: process.env.BUDIBASE_PUBLIC_API_KEY, + BUDIBASE_ACCOUNTS_URL: process.env.BUDIBASE_ACCOUNTS_URL, + BUDIBASE_HOST: process.env.BUDIBASE_HOST, _set(key: any, value: any) { process.env[key] = value module.exports[key] = value diff --git a/qa-core/src/tests/internal-api/applications/applications.spec.ts b/qa-core/src/tests/internal-api/applications/applications.spec.ts deleted file mode 100644 index e7692740b8..0000000000 --- a/qa-core/src/tests/internal-api/applications/applications.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import TestConfiguration from "../../../config/internal-api/TestConfiguration" -import { Application } from "@budibase/server/api/controllers/public/mapping/types" -import { db } from "@budibase/backend-core" -import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" -import { - generateApp, - appFromTemplate, -} from "../../../config/internal-api/fixtures/applications" -import generator from "../../../config/generator" -import generateScreen from "../../../config/internal-api/fixtures/screens" - -describe("Internal API - Application creation, update, publish and delete", () => { - const api = new InternalAPIClient() - const config = new TestConfiguration(api) - - beforeAll(async () => { - await config.loginAsAdmin() - }) - - afterAll(async () => { - await config.afterAll() - }) - - it("Get applications without applications", async () => { - await config.applications.fetchEmptyAppList() - }) - - it("Get all Applications after creating an application", async () => { - await config.applications.create({ - ...generateApp(), - useTemplate: false, - }) - - await config.applications.fetchAllApplications() - }) - - it("Get application details", async () => { - const app = await config.applications.create({ - ...generateApp(), - useTemplate: false, - }) - config.applications.api.appId = app.appId - - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(appPackageJson.application.name).toEqual(app.name) - expect(appPackageJson.application.version).toEqual(app.version) - expect(appPackageJson.application.url).toEqual(app.url) - expect(appPackageJson.application.tenantId).toEqual(app.tenantId) - expect(appPackageJson.application.status).toEqual(app.status) - }) - - it("Publish app", async () => { - // create the app - const app = await config.applications.create(appFromTemplate()) - config.applications.api.appId = app.appId - - // check preview renders - await config.applications.canRender() - - // publish app - await config.applications.publish(app.appId) - - // check published app renders - config.applications.api.appId = db.getProdAppID(app.appId!) - await config.applications.canRender() - - // unpublish app - await config.applications.unpublish(app.appId) - }) - - it("Sync application before deployment", async () => { - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - const [syncResponse, sync] = await config.applications.sync( - app.appId - ) - expect(sync).toEqual({ - message: "App sync not required, app not deployed.", - }) - }) - - it("Sync application after deployment", async () => { - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // publish app - await config.applications.publish(app._id) - - const [syncResponse, sync] = await config.applications.sync( - app.appId - ) - expect(sync).toEqual({ - message: "App sync completed successfully.", - }) - }) - - it("Rename an application", async () => { - const app = await config.applications.create(generateApp()) - - config.applications.api.appId = app.appId - - await config.applications.rename(app.appId, app.name, { - name: generator.word(), - }) - }) - - it("Update the icon and color of an application", async () => { - const app = await config.applications.create(generateApp()) - - config.applications.api.appId = app.appId - - await config.applications.updateIcon(app.appId) - }) - - it("Revert Changes without changes", async () => { - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - await config.applications.revertUnpublished(app.appId) - }) - - it("Revert Changes", async () => { - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // publish app - await config.applications.publish(app._id) - - // Change/add component to the app - await config.screen.create(generateScreen("BASIC")) - - // // Revert the app to published state - await config.applications.revertPublished(app.appId) - - await config.applications.unlock(app.appId) - // Check screen is removed - await config.applications.getRoutes() - }) - - it("Delete an application", async () => { - const app = await config.applications.create(generateApp()) - - await config.applications.delete(app.appId) - }) -}) diff --git a/qa-core/src/tests/internal-api/applications/create.spec.ts b/qa-core/src/tests/internal-api/applications/create.spec.ts new file mode 100644 index 0000000000..4a84eb25ec --- /dev/null +++ b/qa-core/src/tests/internal-api/applications/create.spec.ts @@ -0,0 +1,54 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { db } from "@budibase/backend-core" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" +import generator from "../../../config/generator" +import generateScreen from "../../../config/internal-api/fixtures/screens" + +describe("Internal API - Application creation", () => { + const api = new InternalAPIClient() + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + + beforeAll(async () => { + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Get applications without applications", async () => { + await config.applications.fetchEmptyAppList() + }) + + it("Get all Applications after creating an application", async () => { + await config.applications.create({ + ...generateApp(), + useTemplate: false, + }) + + await config.applications.fetchAllApplications() + }) + + it("Get application details", async () => { + const app = await config.applications.create({ + ...generateApp(), + useTemplate: false, + }) + config.applications.api.appId = app.appId + + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(appPackageJson.application.name).toEqual(app.name) + expect(appPackageJson.application.version).toEqual(app.version) + expect(appPackageJson.application.url).toEqual(app.url) + expect(appPackageJson.application.tenantId).toEqual(app.tenantId) + expect(appPackageJson.application.status).toEqual(app.status) + }) +}) diff --git a/qa-core/src/tests/internal-api/applications/delete.spec.ts b/qa-core/src/tests/internal-api/applications/delete.spec.ts new file mode 100644 index 0000000000..58a1c91da0 --- /dev/null +++ b/qa-core/src/tests/internal-api/applications/delete.spec.ts @@ -0,0 +1,31 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { db } from "@budibase/backend-core" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" +import generator from "../../../config/generator" +import generateScreen from "../../../config/internal-api/fixtures/screens" + +describe("Internal API - Application creation, update, publish and delete", () => { + const api = new InternalAPIClient() + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + + beforeAll(async () => { + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("DELETE - Delete an application", async () => { + const app = await config.applications.create(generateApp()) + + await config.applications.delete(app.appId) + }) +}) diff --git a/qa-core/src/tests/internal-api/applications/publish.spec.ts b/qa-core/src/tests/internal-api/applications/publish.spec.ts new file mode 100644 index 0000000000..ffae642afc --- /dev/null +++ b/qa-core/src/tests/internal-api/applications/publish.spec.ts @@ -0,0 +1,71 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { db } from "@budibase/backend-core" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" +import generator from "../../../config/generator" +import generateScreen from "../../../config/internal-api/fixtures/screens" + +describe("Internal API - Application creation, update, publish and delete", () => { + const api = new InternalAPIClient() + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + + beforeAll(async () => { + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Publish app", async () => { + // create the app + const app = await config.applications.create(appFromTemplate()) + config.applications.api.appId = app.appId + + // check preview renders + await config.applications.canRender() + + // publish app + await config.applications.publish(app.appId) + + // check published app renders + config.applications.api.appId = db.getProdAppID(app.appId!) + await config.applications.canRender() + + // unpublish app + await config.applications.unpublish(app.appId) + }) + + it("Sync application before deployment", async () => { + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [syncResponse, sync] = await config.applications.sync( + app.appId + ) + expect(sync).toEqual({ + message: "App sync not required, app not deployed.", + }) + }) + + it("Sync application after deployment", async () => { + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // publish app + await config.applications.publish(app._id) + + const [syncResponse, sync] = await config.applications.sync( + app.appId + ) + expect(sync).toEqual({ + message: "App sync completed successfully.", + }) + }) +}) diff --git a/qa-core/src/tests/internal-api/applications/update.spec.ts b/qa-core/src/tests/internal-api/applications/update.spec.ts new file mode 100644 index 0000000000..28c92ba2d7 --- /dev/null +++ b/qa-core/src/tests/internal-api/applications/update.spec.ts @@ -0,0 +1,59 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import { db } from "@budibase/backend-core" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { + generateApp, + appFromTemplate, +} from "../../../config/internal-api/fixtures/applications" +import generator from "../../../config/generator" +import generateScreen from "../../../config/internal-api/fixtures/screens" + +describe("Internal API - Application creation, update, publish and delete", () => { + const api = new InternalAPIClient() + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + + beforeAll(async () => { + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Update an application", async () => { + const app = await config.applications.create(generateApp()) + + config.applications.api.appId = app.appId + + await config.applications.rename(app.appId, app.name, { + name: generator.word(), + }) + }) + + it("Revert Changes without changes", async () => { + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + await config.applications.revertUnpublished(app.appId) + }) + + it("Revert Changes", async () => { + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // publish app + await config.applications.publish(app._id) + + // Change/add component to the app + await config.screen.create(generateScreen("BASIC")) + + // // Revert the app to published state + await config.applications.revertPublished(app.appId) + + // Check screen is removed + await config.applications.getRoutes() + }) +}) diff --git a/qa-core/src/tests/internal-api/screens/screens.spec.ts b/qa-core/src/tests/internal-api/screens/screens.spec.ts index ee3e97afc6..cfc2b7763d 100644 --- a/qa-core/src/tests/internal-api/screens/screens.spec.ts +++ b/qa-core/src/tests/internal-api/screens/screens.spec.ts @@ -1,20 +1,19 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { App } from "@budibase/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" -import { - generateApp, - appFromTemplate, -} from "../../../config/internal-api/fixtures/applications" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { generateApp } from "../../../config/internal-api/fixtures/applications" import { Screen } from "@budibase/types" import generateScreen from "../../../config/internal-api/fixtures/screens" describe("Internal API - /screens endpoints", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) - const appConfig = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) + const appConfig = new TestConfiguration(api, accountsAPI) beforeAll(async () => { - await config.loginAsAdmin() + await config.setupAccountAndTenant() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/tables/tables.spec.ts b/qa-core/src/tests/internal-api/tables/tables.spec.ts index 7d79330304..e3375aa24c 100644 --- a/qa-core/src/tests/internal-api/tables/tables.spec.ts +++ b/qa-core/src/tests/internal-api/tables/tables.spec.ts @@ -1,6 +1,7 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import generator from "../../../config/generator" import { generateTable, @@ -13,10 +14,11 @@ import { describe("Internal API - Table Operations", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) beforeAll(async () => { - await config.loginAsAdmin() + await config.setupAccountAndTenant() }) afterAll(async () => { diff --git a/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts index 53ae78f42d..63d70cf141 100644 --- a/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts +++ b/qa-core/src/tests/internal-api/userManagement/appSpecificRoles.spec.ts @@ -1,39 +1,39 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateApp, appFromTemplate, } from "../../../config/internal-api/fixtures/applications" import { generateUser } from "../../../config/internal-api/fixtures/userManagement" import { User } from "@budibase/types" -import { - generateNewColumnForTable, - generateTable, -} from "../../../config/internal-api/fixtures/table" -import generateScreen from "../../../config/internal-api/fixtures/screens" import { db } from "@budibase/backend-core" describe("Internal API - App Specific Roles & Permissions", () => { - const api = new InternalAPIClient() - const config = new TestConfiguration(api) + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration // Before each test, login as admin. Some tests will require login as a different user - beforeEach(async () => { - await config.loginAsAdmin() + beforeAll(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() }) + afterAll(async () => { + await config.afterAll() + }) afterAll(async () => { await config.afterAll() }) it("Add BASIC user to app", async () => { - // Create a user with BASIC role and check if it was created successfully const appUser = generateUser() expect(appUser[0].builder?.global).toEqual(false) expect(appUser[0].admin?.global).toEqual(false) - - // Add the user to the tenant. const [createUserResponse, createUserJson] = await config.users.addMultiple( appUser ) @@ -41,12 +41,9 @@ describe("Internal API - App Specific Roles & Permissions", () => { const app = await config.applications.create(appFromTemplate()) config.applications.api.appId = app.appId - // Get all the information from the create user const [userInfoResponse, userInfoJson] = await config.users.getInfo( createUserJson.created.successful[0]._id ) - - // Create the body with the information from the user and add the role to the app const body: User = { ...userInfoJson, roles: { @@ -55,7 +52,6 @@ describe("Internal API - App Specific Roles & Permissions", () => { } await config.users.updateInfo(body) - // Get the user information again and check if the role was added const [changedUserInfoResponse, changedUserInfoJson] = await config.users.getInfo(createUserJson.created.successful[0]._id) expect(changedUserInfoJson.roles[app.appId]).toBeDefined() @@ -74,12 +70,9 @@ describe("Internal API - App Specific Roles & Permissions", () => { const app = await config.applications.create(appFromTemplate()) config.applications.api.appId = app.appId - // Get all the information from the create user const [userInfoResponse, userInfoJson] = await config.users.getInfo( createUserJson.created.successful[0]._id ) - - // Create the body with the information from the user and add the role to the app const body: User = { ...userInfoJson, roles: { @@ -88,7 +81,6 @@ describe("Internal API - App Specific Roles & Permissions", () => { } await config.users.updateInfo(body) - // Get the user information again and check if the role was added const [changedUserInfoResponse, changedUserInfoJson] = await config.users.getInfo(createUserJson.created.successful[0]._id) expect(changedUserInfoJson.roles[app.appId]).toBeDefined() @@ -102,9 +94,9 @@ describe("Internal API - App Specific Roles & Permissions", () => { }) it("Add POWER user to app", async () => { - // Create a user with POWER role and check if it was created successfully const powerUser = generateUser(1, "developer") expect(powerUser[0].builder?.global).toEqual(true) + const [createUserResponse, createUserJson] = await config.users.addMultiple( powerUser ) @@ -112,12 +104,9 @@ describe("Internal API - App Specific Roles & Permissions", () => { const app = await config.applications.create(generateApp()) config.applications.api.appId = app.appId - // Get all the information from the create user const [userInfoResponse, userInfoJson] = await config.users.getInfo( createUserJson.created.successful[0]._id ) - - // Create the body with the information from the user and add the role to the app const body: User = { ...userInfoJson, roles: { @@ -132,609 +121,4 @@ describe("Internal API - App Specific Roles & Permissions", () => { expect(changedUserInfoJson.roles[app.appId]).toBeDefined() expect(changedUserInfoJson.roles[app.appId]).toEqual("POWER") }) - - describe("Check Access for default roles", () => { - it("Check Table access for app user", async () => { - // Create a user with BASIC role and check if it was created successfully - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Get all the information from the create user - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - - // Create the body with the information from the user and add the role to the app - const body: User = { - ...userInfoJson, - roles: { - [app.appId]: "BASIC", - }, - } - await config.users.updateInfo(body) - - // Get the user information again and check if the role was added - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[app.appId]).toBeDefined() - expect(changedUserInfoJson.roles[app.appId]).toEqual("BASIC") - - // Create a table - const [createdTableResponse, createdTableData] = await config.tables.save( - generateTable() - ) - - // Login with the user created and try to create a column - await config.login(appUser[0].email, appUser[0].password) - const newColumn = generateNewColumnForTable(createdTableData) - await config.tables.forbiddenSave(newColumn) - await config.tables.forbiddenSave(generateTable()) - }) - - it("Check Table access for developer", async () => { - // Create a user with POWER role and check if it was created successfully - const developer = generateUser(1, "developer") - expect(developer[0].builder?.global).toEqual(true) - - const [createUserResponse, createUserJson] = - await config.users.addMultiple(developer) - - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Get all the information from the create user - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - - // Create the body with the information from the user and add the role to the app - const body: User = { - ...userInfoJson, - roles: { - [app.appId]: "POWER", - }, - } - await config.users.updateInfo(body) - - // Get the user information again and check if the role was added - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[app.appId]).toBeDefined() - expect(changedUserInfoJson.roles[app.appId]).toEqual("POWER") - - // Create a table - const [createdTableResponse, createdTableData] = await config.tables.save( - generateTable() - ) - - // Login with the user created and try to create a column - await config.login( - developer[0].email, - developer[0].password - ) - const newColumn = generateNewColumnForTable(createdTableData) - const [addColumnResponse, addColumnData] = await config.tables.save( - newColumn, - true - ) - }) - - it("Check Table access for admin", async () => { - // Create a user with ADMIN role and check if it was created successfully - const adminUser = generateUser(1, "admin") - expect(adminUser[0].builder?.global).toEqual(true) - expect(adminUser[0].admin?.global).toEqual(true) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(adminUser) - - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Get all the information from the create user - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - - // Create the body with the information from the user and add the role to the app - const body: User = { - ...userInfoJson, - roles: { - [app.appId]: "ADMIN", - }, - } - await config.users.updateInfo(body) - - // Get the user information again and check if the role was added - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[app.appId]).toBeDefined() - expect(changedUserInfoJson.roles[app.appId]).toEqual("ADMIN") - - // Login with the created user and create a table - await config.login( - adminUser[0].email, - adminUser[0].password - ) - const [createdTableResponse, createdTableData] = await config.tables.save( - generateTable() - ) - const newColumn = generateNewColumnForTable(createdTableData) - const [addColumnResponse, addColumnData] = await config.tables.save( - newColumn, - true - ) - }) - }) - - describe("Screen Access for App specific roles", () => { - it("Check Screen access for BASIC Role", async () => { - // Set up user - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: "BASIC", - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual("BASIC") - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with BASIC user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - expect(appPackageJson.screens[0].routing.roleId).toEqual("BASIC") - }) - - it("Check Screen access for POWER role", async () => { - // Set up user - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: "POWER", - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual("POWER") - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with POWER user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(2) - }) - - it("Check Screen access for ADMIN role", async () => { - // Set up user - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: "ADMIN", - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual("ADMIN") - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with ADMIN user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(3) - }) - }) - describe("Screen Access for custom roles", () => { - it("Custom role access for level 1 permissions", async () => { - // Set up user - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - //Create level 1 role - const role = { - inherits: "BASIC", - permissionId: "public", - name: "level 1", - } - const [createRoleResponse, createRoleJson] = - await config.users.createRole(role) - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: createRoleJson._id, - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with level 1 user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - }) - it("Custom role access for level 2 permissions", async () => { - // Set up user - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - //Create level 1 role - const role = { - inherits: "BASIC", - permissionId: "read_only", - name: "level 2", - } - const [createRoleResponse, createRoleJson] = - await config.users.createRole(role) - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: createRoleJson._id, - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with level 1 user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - }) - it("Custom role access for level 3 permissions", async () => { - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - //Create level 1 role - const role = { - inherits: "BASIC", - permissionId: "write", - name: "level 3", - } - const [createRoleResponse, createRoleJson] = - await config.users.createRole(role) - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: createRoleJson._id, - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with level 1 user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - }) - it("Custom role access for level 4 permissions", async () => { - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - //Create level 1 role - const role = { - inherits: "BASIC", - permissionId: "power", - name: "level 4", - } - const [createRoleResponse, createRoleJson] = - await config.users.createRole(role) - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: createRoleJson._id, - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with level 1 user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - }) - it("Custom role access for level 5 permissions", async () => { - const appUser = generateUser() - expect(appUser[0].builder?.global).toEqual(false) - expect(appUser[0].admin?.global).toEqual(false) - const [createUserResponse, createUserJson] = - await config.users.addMultiple(appUser) - - // Create App - const app = await config.applications.create(generateApp()) - config.applications.api.appId = app.appId - - //Create level 1 role - const role = { - inherits: "BASIC", - permissionId: "admin", - name: "level 5", - } - const [createRoleResponse, createRoleJson] = - await config.users.createRole(role) - - // Update user roles - const [userInfoResponse, userInfoJson] = await config.users.getInfo( - createUserJson.created.successful[0]._id - ) - const prodAppId = db.getProdAppID(app.appId!) - - // Roles must always be set with prod appID - const body: User = { - ...userInfoJson, - roles: { - [prodAppId]: createRoleJson._id, - }, - } - await config.users.updateInfo(body) - - const [changedUserInfoResponse, changedUserInfoJson] = - await config.users.getInfo(createUserJson.created.successful[0]._id) - expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() - expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) - - await config.screen.create(generateScreen("BASIC")) - await config.screen.create(generateScreen("POWER")) - await config.screen.create(generateScreen("ADMIN")) - - await config.applications.publish(app.appId) - const [firstappPackageResponse, firstappPackageJson] = - await config.applications.getAppPackage(app.appId) - expect(firstappPackageJson.screens).toBeDefined() - expect(firstappPackageJson.screens.length).toEqual(3) - - // login with level 1 user - await config.login(appUser[0].email!, appUser[0].password!) - const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() - - // fetch app package - const [appPackageResponse, appPackageJson] = - await config.applications.getAppPackage(app.appId!) - expect(appPackageJson.screens).toBeDefined() - expect(appPackageJson.screens.length).toEqual(1) - }) - }) }) diff --git a/qa-core/src/tests/internal-api/userManagement/customRoles.spec.ts b/qa-core/src/tests/internal-api/userManagement/customRoles.spec.ts new file mode 100644 index 0000000000..d672bc0eb2 --- /dev/null +++ b/qa-core/src/tests/internal-api/userManagement/customRoles.spec.ts @@ -0,0 +1,332 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { generateApp } from "../../../config/internal-api/fixtures/applications" +import { generateUser } from "../../../config/internal-api/fixtures/userManagement" +import { App, User } from "@budibase/types" +import generateScreen from "../../../config/internal-api/fixtures/screens" +import { db } from "@budibase/backend-core" + +describe.skip("Internal API - App Specific Roles & Permissions", () => { + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration + let app: Partial + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() + app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Custom role access for level 1 permissions", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "public", + name: "level 1", + } + const [createRoleResponse, createRoleJson] = await config.users.createRole( + role + ) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 2 permissions", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "read_only", + name: "level 2", + } + const [createRoleResponse, createRoleJson] = await config.users.createRole( + role + ) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 3 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "write", + name: "level 3", + } + const [createRoleResponse, createRoleJson] = await config.users.createRole( + role + ) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 4 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "power", + name: "level 4", + } + const [createRoleResponse, createRoleJson] = await config.users.createRole( + role + ) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) + it("Custom role access for level 5 permissions", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + + //Create level 1 role + const role = { + inherits: "BASIC", + permissionId: "admin", + name: "level 5", + } + const [createRoleResponse, createRoleJson] = await config.users.createRole( + role + ) + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: createRoleJson._id, + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual(createRoleJson._id) + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with level 1 user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + }) +}) diff --git a/qa-core/src/tests/internal-api/userManagement/screenAccess.spec.ts b/qa-core/src/tests/internal-api/userManagement/screenAccess.spec.ts new file mode 100644 index 0000000000..26f9d25ff0 --- /dev/null +++ b/qa-core/src/tests/internal-api/userManagement/screenAccess.spec.ts @@ -0,0 +1,191 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { generateApp } from "../../../config/internal-api/fixtures/applications" +import { generateUser } from "../../../config/internal-api/fixtures/userManagement" +import { User } from "@budibase/types" +import generateScreen from "../../../config/internal-api/fixtures/screens" +import { db } from "@budibase/backend-core" + +describe.skip("Internal API - Role screen access", () => { + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Check Screen access for BASIC Role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "BASIC", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("BASIC") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with BASIC user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(1) + expect(appPackageJson.screens[0].routing.roleId).toEqual("BASIC") + }) + + it("Check Screen access for POWER role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "POWER", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("POWER") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with POWER user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(2) + }) + + it("Check Screen access for ADMIN role", async () => { + // Set up user + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + // Create App + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + // Update user roles + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const prodAppId = db.getProdAppID(app.appId!) + + // Roles must always be set with prod appID + const body: User = { + ...userInfoJson, + roles: { + [prodAppId]: "ADMIN", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[prodAppId]).toBeDefined() + expect(changedUserInfoJson.roles[prodAppId]).toEqual("ADMIN") + + await config.screen.create(generateScreen("BASIC")) + await config.screen.create(generateScreen("POWER")) + await config.screen.create(generateScreen("ADMIN")) + + await config.applications.publish(app.appId) + const [firstappPackageResponse, firstappPackageJson] = + await config.applications.getAppPackage(app.appId) + expect(firstappPackageJson.screens).toBeDefined() + expect(firstappPackageJson.screens.length).toEqual(3) + + // login with ADMIN user + await config.login(appUser[0].email!, appUser[0].password!) + const [selfInfoResponse, selfInfoJson] = await config.users.getSelf() + + // fetch app package + const [appPackageResponse, appPackageJson] = + await config.applications.getAppPackage(app.appId!) + expect(appPackageJson.screens).toBeDefined() + expect(appPackageJson.screens.length).toEqual(3) + }) +}) diff --git a/qa-core/src/tests/internal-api/userManagement/tableAccess.spec.ts b/qa-core/src/tests/internal-api/userManagement/tableAccess.spec.ts new file mode 100644 index 0000000000..93e75348bd --- /dev/null +++ b/qa-core/src/tests/internal-api/userManagement/tableAccess.spec.ts @@ -0,0 +1,149 @@ +import TestConfiguration from "../../../config/internal-api/TestConfiguration" +import { Application } from "@budibase/server/api/controllers/public/mapping/types" +import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" +import { generateApp } from "../../../config/internal-api/fixtures/applications" +import { generateUser } from "../../../config/internal-api/fixtures/userManagement" +import { User } from "@budibase/types" +import { + generateNewColumnForTable, + generateTable, +} from "../../../config/internal-api/fixtures/table" + +describe.skip("Internal API - Role table access", () => { + let api: InternalAPIClient + let accountsAPI: AccountsAPIClient + let config: TestConfiguration + + // Before each test, login as admin. Some tests will require login as a different user + beforeEach(async () => { + api = new InternalAPIClient() + accountsAPI = new AccountsAPIClient() + config = new TestConfiguration(api, accountsAPI) + await config.setupAccountAndTenant() + }) + + afterAll(async () => { + await config.afterAll() + }) + + it("Check Table access for app user", async () => { + const appUser = generateUser() + expect(appUser[0].builder?.global).toEqual(false) + expect(appUser[0].admin?.global).toEqual(false) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + appUser + ) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "BASIC", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("BASIC") + + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + + await config.login(appUser[0].email, appUser[0].password) + + const newColumn = generateNewColumnForTable(createdTableData) + await config.tables.forbiddenSave(newColumn) + await config.tables.forbiddenSave(generateTable()) + }) + + it.skip("Check Table access for developer", async () => { + const developer = generateUser(1, "developer") + expect(developer[0].builder?.global).toEqual(true) + + const [createUserResponse, createUserJson] = await config.users.addMultiple( + developer + ) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "POWER", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("POWER") + + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + await config.login( + developer[0].email, + developer[0].password + ) + const newColumn = generateNewColumnForTable(createdTableData) + const [addColumnResponse, addColumnData] = await config.tables.save( + newColumn, + true + ) + }) + + it.skip("Check Table access for admin", async () => { + const adminUser = generateUser(1, "admin") + expect(adminUser[0].builder?.global).toEqual(true) + expect(adminUser[0].admin?.global).toEqual(true) + const [createUserResponse, createUserJson] = await config.users.addMultiple( + adminUser + ) + + const app = await config.applications.create(generateApp()) + config.applications.api.appId = app.appId + + const [userInfoResponse, userInfoJson] = await config.users.getInfo( + createUserJson.created.successful[0]._id + ) + const body: User = { + ...userInfoJson, + roles: { + [app.appId]: "ADMIN", + }, + } + await config.users.updateInfo(body) + + const [changedUserInfoResponse, changedUserInfoJson] = + await config.users.getInfo(createUserJson.created.successful[0]._id) + expect(changedUserInfoJson.roles[app.appId]).toBeDefined() + expect(changedUserInfoJson.roles[app.appId]).toEqual("ADMIN") + + await config.login( + adminUser[0].email, + adminUser[0].password + ) + const [createdTableResponse, createdTableData] = await config.tables.save( + generateTable() + ) + const newColumn = generateNewColumnForTable(createdTableData) + const [addColumnResponse, addColumnData] = await config.tables.save( + newColumn, + true + ) + }) +}) diff --git a/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts index fb61d5070f..591f6dfe02 100644 --- a/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts +++ b/qa-core/src/tests/internal-api/userManagement/userManagement.spec.ts @@ -1,16 +1,18 @@ import TestConfiguration from "../../../config/internal-api/TestConfiguration" import { Application } from "@budibase/server/api/controllers/public/mapping/types" import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient" +import AccountsAPIClient from "../../../config/internal-api/TestConfiguration/accountsAPIClient" import { generateUser } from "../../../config/internal-api/fixtures/userManagement" import { User } from "@budibase/types" describe("Internal API - User Management & Permissions", () => { const api = new InternalAPIClient() - const config = new TestConfiguration(api) + const accountsAPI = new AccountsAPIClient() + const config = new TestConfiguration(api, accountsAPI) // Before each test, login as admin. Some tests will require login as a different user - beforeEach(async () => { - await config.loginAsAdmin() + beforeAll(async () => { + await config.setupAccountAndTenant() }) afterAll(async () => {