diff --git a/.eslintrc.json b/.eslintrc.json
index 8eccb147a5..87f8269c50 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -16,8 +16,7 @@
"dist",
"public",
"*.spec.js",
- "bundle.js",
- "coverage"
+ "bundle.js"
],
"plugins": ["svelte3"],
"extends": ["eslint:recommended"],
diff --git a/packages/backend-core/__mocks__/node-fetch.ts b/packages/backend-core/__mocks__/node-fetch.ts
deleted file mode 100644
index 4c7127ee48..0000000000
--- a/packages/backend-core/__mocks__/node-fetch.ts
+++ /dev/null
@@ -1 +0,0 @@
-jest.mock("node-fetch", () => jest.fn())
diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json
index 63adcfc19c..0239acb9a1 100644
--- a/packages/backend-core/package.json
+++ b/packages/backend-core/package.json
@@ -56,7 +56,7 @@
"@types/chance": "1.1.3",
"@types/ioredis": "4.28.0",
"@types/jest": "27.5.1",
- "@types/koa": "2.0.52",
+ "@types/koa": "2.13.4",
"@types/lodash": "4.14.180",
"@types/node": "14.18.20",
"@types/node-fetch": "2.6.1",
@@ -68,7 +68,7 @@
"chance": "1.1.3",
"ioredis-mock": "5.8.0",
"jest": "28.1.1",
- "koa": "2.7.0",
+ "koa": "2.13.4",
"nodemon": "2.0.16",
"pouchdb-adapter-memory": "7.2.2",
"timekeeper": "2.2.0",
diff --git a/packages/backend-core/src/middleware/tests/matchers.spec.ts b/packages/backend-core/src/middleware/tests/matchers.spec.ts
new file mode 100644
index 0000000000..c39bbb6dd3
--- /dev/null
+++ b/packages/backend-core/src/middleware/tests/matchers.spec.ts
@@ -0,0 +1,134 @@
+import * as matchers from "../matchers"
+import { structures } from "../../../tests"
+
+describe("matchers", () => {
+ it("matches by path and method", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "POST",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests"
+ ctx.request.method = "POST"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(true)
+ })
+
+ it("wildcards path", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "POST",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests/id/something/else"
+ ctx.request.method = "POST"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(true)
+ })
+
+ it("doesn't wildcard path with strict", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "POST",
+ strict: true,
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests/id/something/else"
+ ctx.request.method = "POST"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(false)
+ })
+
+ it("matches with param", () => {
+ const pattern = [
+ {
+ route: "/api/tests/:testId",
+ method: "GET",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests/id"
+ ctx.request.method = "GET"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(true)
+ })
+
+ // TODO: Support the below behaviour
+ // Strict does not work when a param is present
+ // it("matches with param with strict", () => {
+ // const pattern = [{
+ // route: "/api/tests/:testId",
+ // method: "GET",
+ // strict: true
+ // }]
+ // const ctx = structures.koa.newContext()
+ // ctx.request.url = "/api/tests/id"
+ // ctx.request.method = "GET"
+ //
+ // const built = matchers.buildMatcherRegex(pattern)
+ //
+ // expect(!!matchers.matches(ctx, built)).toBe(true)
+ // })
+
+ it("doesn't match by path", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "POST",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/unknown"
+ ctx.request.method = "POST"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(false)
+ })
+
+ it("doesn't match by method", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "POST",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests"
+ ctx.request.method = "GET"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(false)
+ })
+
+ it("matches by path and wildcard method", () => {
+ const pattern = [
+ {
+ route: "/api/tests",
+ method: "ALL",
+ },
+ ]
+ const ctx = structures.koa.newContext()
+ ctx.request.url = "/api/tests"
+ ctx.request.method = "GET"
+
+ const built = matchers.buildMatcherRegex(pattern)
+
+ expect(!!matchers.matches(ctx, built)).toBe(true)
+ })
+})
diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts
index 99955ca321..3ac0f5c314 100644
--- a/packages/backend-core/src/tenancy/tenancy.ts
+++ b/packages/backend-core/src/tenancy/tenancy.ts
@@ -231,12 +231,18 @@ export const getTenantIDFromCtx = (
const match = ctx.matched.find(
(m: any) => !!m.paramNames.find((p: any) => p.name === "tenantId")
)
+
+ // get the raw path url - without any query params
+ const ctxUrl = ctx.originalUrl
+ let url
+ if (ctxUrl.includes("?")) {
+ url = ctxUrl.split("?")[0]
+ } else {
+ url = ctxUrl
+ }
+
if (match) {
- const params = match.params(
- ctx.originalUrl,
- match.captures(ctx.originalUrl),
- {}
- )
+ const params = match.params(url, match.captures(url), {})
if (params.tenantId) {
return params.tenantId
}
diff --git a/packages/backend-core/src/utils.ts b/packages/backend-core/src/utils.ts
index 215288b93c..3b9bd611d4 100644
--- a/packages/backend-core/src/utils.ts
+++ b/packages/backend-core/src/utils.ts
@@ -30,8 +30,8 @@ async function resolveAppUrl(ctx: BBContext) {
let possibleAppUrl = `/${appUrl.toLowerCase()}`
let tenantId = tenancy.getTenantId()
- if (!env.SELF_HOSTED) {
- // always use the tenant id from the subdomain in cloud
+ if (env.MULTI_TENANCY) {
+ // always use the tenant id from the subdomain in multi tenancy
// this ensures the logged-in user tenant id doesn't overwrite
// e.g. in the case of viewing a public app while already logged-in to another tenant
tenantId = tenancy.getTenantIDFromCtx(ctx, {
diff --git a/packages/backend-core/tests/utilities/mocks/fetch.ts b/packages/backend-core/tests/utilities/mocks/fetch.ts
new file mode 100644
index 0000000000..573b47db9f
--- /dev/null
+++ b/packages/backend-core/tests/utilities/mocks/fetch.ts
@@ -0,0 +1,4 @@
+const mockFetch = jest.fn()
+jest.mock("node-fetch", () => mockFetch)
+
+export default mockFetch
diff --git a/packages/backend-core/tests/utilities/mocks/index.ts b/packages/backend-core/tests/utilities/mocks/index.ts
index 7031b225ec..e71c739e26 100644
--- a/packages/backend-core/tests/utilities/mocks/index.ts
+++ b/packages/backend-core/tests/utilities/mocks/index.ts
@@ -2,3 +2,4 @@ import "./posthog"
import "./events"
export * as accounts from "./accounts"
export * as date from "./date"
+export { default as fetch } from "./fetch"
diff --git a/packages/backend-core/tests/utilities/structures/koa.ts b/packages/backend-core/tests/utilities/structures/koa.ts
index 6f0f7866e6..7084c90360 100644
--- a/packages/backend-core/tests/utilities/structures/koa.ts
+++ b/packages/backend-core/tests/utilities/structures/koa.ts
@@ -1,5 +1,13 @@
import { createMockContext } from "@shopify/jest-koa-mocks"
+import { BBContext } from "@budibase/types"
-export const newContext = () => {
- return createMockContext()
+export const newContext = (): BBContext => {
+ const ctx = createMockContext()
+ return {
+ ...ctx,
+ request: {
+ ...ctx.request,
+ body: {},
+ },
+ }
}
diff --git a/packages/backend-core/yarn.lock b/packages/backend-core/yarn.lock
index 77216a9069..fd48d574b7 100644
--- a/packages/backend-core/yarn.lock
+++ b/packages/backend-core/yarn.lock
@@ -1079,7 +1079,7 @@
dependencies:
"@types/koa" "*"
-"@types/koa@*":
+"@types/koa@*", "@types/koa@2.13.4":
version "2.13.4"
resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.13.4.tgz#10620b3f24a8027ef5cbae88b393d1b31205726b"
integrity sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==
@@ -1093,18 +1093,6 @@
"@types/koa-compose" "*"
"@types/node" "*"
-"@types/koa@2.0.52":
- version "2.0.52"
- resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.52.tgz#7dd11de4189ab339ad66c4ccad153716b14e525f"
- integrity sha512-cp/GTOhOYwomlSKqEoG0kaVEVJEzP4ojYmfa7EKaGkmkkRwJ4B/1VBLbQZ49Z+WJNvzXejQB/9GIKqMo9XLgFQ==
- dependencies:
- "@types/accepts" "*"
- "@types/cookies" "*"
- "@types/http-assert" "*"
- "@types/keygrip" "*"
- "@types/koa-compose" "*"
- "@types/node" "*"
-
"@types/lodash@4.14.180":
version "4.14.180"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670"
@@ -1478,11 +1466,6 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
-any-promise@^1.1.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
- integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
-
anymatch@^3.0.3, anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
@@ -2056,14 +2039,6 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
dependencies:
safe-buffer "~5.1.1"
-cookies@~0.7.1:
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.3.tgz#7912ce21fbf2e8c2da70cf1c3f351aecf59dadfa"
- integrity sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==
- dependencies:
- depd "~1.1.2"
- keygrip "~1.0.3"
-
cookies@~0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90"
@@ -2134,13 +2109,6 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@~3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
- integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
- dependencies:
- ms "2.0.0"
-
debuglog@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -2201,7 +2169,7 @@ denque@^1.1.0:
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
-depd@^1.1.0, depd@^1.1.2, depd@~1.1.2:
+depd@^1.1.0, depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
@@ -2353,11 +2321,6 @@ error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
-error-inject@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/error-inject/-/error-inject-1.0.0.tgz#e2b3d91b54aed672f309d950d154850fa11d4f37"
- integrity sha512-JM8N6PytDbmIYm1IhPWlo8vr3NtfjhDY/1MhD/a5b/aad/USE8a0+NsqE9d5n+GVGmuNkPQWm4bFQWv18d8tMg==
-
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -3602,11 +3565,6 @@ jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
jwa "^1.4.1"
safe-buffer "^5.0.1"
-keygrip@~1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc"
- integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==
-
keygrip@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
@@ -3626,26 +3584,11 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-koa-compose@^3.0.0:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7"
- integrity sha512-8gen2cvKHIZ35eDEik5WOo8zbVp9t4cP8p4hW4uE55waxolLRexKKrqfCpwhGVppnB40jWeF8bZeTVg99eZgPw==
- dependencies:
- any-promise "^1.1.0"
-
koa-compose@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
-koa-convert@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0"
- integrity sha512-K9XqjmEDStGX09v3oxR7t5uPRy0jqJdvodHa6wxWTHrTfDq0WUNnYTOOUZN6g8OM8oZQXprQASbiIXG2Ez8ehA==
- dependencies:
- co "^4.6.0"
- koa-compose "^3.0.0"
-
koa-convert@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
@@ -3654,11 +3597,6 @@ koa-convert@^2.0.0:
co "^4.6.0"
koa-compose "^4.1.0"
-koa-is-json@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/koa-is-json/-/koa-is-json-1.0.0.tgz#273c07edcdcb8df6a2c1ab7d59ee76491451ec14"
- integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw==
-
koa-passport@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/koa-passport/-/koa-passport-4.1.4.tgz#5f1665c1c2a37ace79af9f970b770885ca30ccfa"
@@ -3666,37 +3604,7 @@ koa-passport@4.1.4:
dependencies:
passport "^0.4.0"
-koa@2.7.0:
- version "2.7.0"
- resolved "https://registry.yarnpkg.com/koa/-/koa-2.7.0.tgz#7e00843506942b9d82c6cc33749f657c6e5e7adf"
- integrity sha512-7ojD05s2Q+hFudF8tDLZ1CpCdVZw8JQELWSkcfG9bdtoTDzMmkRF6BQBU7JzIzCCOY3xd3tftiy/loHBUYaY2Q==
- dependencies:
- accepts "^1.3.5"
- cache-content-type "^1.0.0"
- content-disposition "~0.5.2"
- content-type "^1.0.4"
- cookies "~0.7.1"
- debug "~3.1.0"
- delegates "^1.0.0"
- depd "^1.1.2"
- destroy "^1.0.4"
- error-inject "^1.0.0"
- escape-html "^1.0.3"
- fresh "~0.5.2"
- http-assert "^1.3.0"
- http-errors "^1.6.3"
- is-generator-function "^1.0.7"
- koa-compose "^4.1.0"
- koa-convert "^1.2.0"
- koa-is-json "^1.0.0"
- on-finished "^2.3.0"
- only "~0.0.2"
- parseurl "^1.3.2"
- statuses "^1.5.0"
- type-is "^1.6.16"
- vary "^1.1.2"
-
-koa@^2.13.4:
+koa@2.13.4, koa@^2.13.4:
version "2.13.4"
resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.4.tgz#ee5b0cb39e0b8069c38d115139c774833d32462e"
integrity sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==
@@ -4079,11 +3987,6 @@ mkdirp@^1.0.3:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
diff --git a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
index dab0bfdd90..116fdeff28 100644
--- a/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
+++ b/packages/builder/src/pages/builder/portal/manage/auth/index.svelte
@@ -20,11 +20,12 @@
Toggle,
Tag,
Tags,
+ Icon,
+ Helpers,
} from "@budibase/bbui"
import { onMount } from "svelte"
import { API } from "api"
import { organisation, admin } from "stores/portal"
- import { Helpers } from "@budibase/bbui"
const ConfigTypes = {
Google: "google",
@@ -40,7 +41,9 @@
// Indicate to user that callback is based on platform url
// If there is an existing value, indicate that it may be removed to return to default behaviour
- $: googleCallbackTooltip = googleCallbackReadonly
+ $: googleCallbackTooltip = $admin.cloud
+ ? null
+ : googleCallbackReadonly
? "Vist the organisation page to update the platform URL"
: "Leave blank to use the default callback URL"
@@ -54,6 +57,7 @@
readonly: googleCallbackReadonly,
tooltip: googleCallbackTooltip,
placeholder: $organisation.googleCallbackUrl,
+ copyButton: true,
},
],
}
@@ -66,9 +70,12 @@
{
name: "callbackURL",
readonly: true,
- tooltip: "Vist the organisation page to update the platform URL",
+ tooltip: $admin.cloud
+ ? null
+ : "Vist the organisation page to update the platform URL",
label: "Callback URL",
placeholder: $organisation.oidcCallbackUrl,
+ copyButton: true,
},
],
}
@@ -231,6 +238,11 @@
},
]
+ const copyToClipboard = async value => {
+ await Helpers.copyToClipboard(value)
+ notifications.success("Copied")
+ }
+
onMount(async () => {
try {
await organisation.init()
@@ -336,11 +348,23 @@
{#each GoogleConfigFields.Google as field}
{/each}
@@ -375,12 +399,23 @@
{#each OIDCConfigFields.Oidc as field}
{/each}
@@ -557,4 +592,16 @@
.provider-title span {
flex: 1 1 auto;
}
+ .inputContainer {
+ display: flex;
+ flex-direction: row;
+ }
+ .input {
+ flex: 1;
+ }
+ .copy {
+ display: flex;
+ align-items: center;
+ margin-left: 10px;
+ }
diff --git a/packages/types/src/sdk/auth.ts b/packages/types/src/sdk/auth.ts
index d61b679c62..766d18a606 100644
--- a/packages/types/src/sdk/auth.ts
+++ b/packages/types/src/sdk/auth.ts
@@ -31,5 +31,5 @@ export interface ScannedSession {
export interface PlatformLogoutOpts {
ctx: BBContext
userId: string
- keepActiveSession: boolean
+ keepActiveSession?: boolean
}
diff --git a/packages/worker/__mocks__/node-fetch.ts b/packages/worker/__mocks__/node-fetch.ts
deleted file mode 100644
index 4c7127ee48..0000000000
--- a/packages/worker/__mocks__/node-fetch.ts
+++ /dev/null
@@ -1 +0,0 @@
-jest.mock("node-fetch", () => jest.fn())
diff --git a/packages/worker/__mocks__/oauth.ts b/packages/worker/__mocks__/oauth.ts
new file mode 100644
index 0000000000..8e8122a9e0
--- /dev/null
+++ b/packages/worker/__mocks__/oauth.ts
@@ -0,0 +1,57 @@
+import * as jwt from "jsonwebtoken"
+
+const mockOAuth2 = {
+ getOAuthAccessToken: (code: string, p: any, cb: any) => {
+ const err = null
+ const accessToken = "access_token"
+ const refreshToken = "refresh_token"
+
+ const exp = new Date()
+ exp.setDate(exp.getDate() + 1)
+
+ const iat = new Date()
+ iat.setDate(iat.getDate() - 1)
+
+ const claims = {
+ iss: "test",
+ sub: "sub",
+ aud: "clientId",
+ exp: exp.getTime() / 1000,
+ iat: iat.getTime() / 1000,
+ email: "oauth@example.com",
+ }
+
+ const idToken = jwt.sign(claims, "secret")
+
+ const params = {
+ id_token: idToken,
+ }
+ return cb(err, accessToken, refreshToken, params)
+ },
+ _request: (
+ method: string,
+ url: string,
+ headers: any,
+ postBody: any,
+ accessToken: string,
+ cb: any
+ ) => {
+ const err = null
+ const body = {
+ sub: "sub",
+ user_id: "userId",
+ name: "OAuth",
+ family_name: "2",
+ given_name: "OAuth",
+ middle_name: "",
+ }
+ const res = {}
+ return cb(err, JSON.stringify(body), res)
+ },
+}
+
+const oauth = {
+ OAuth2: jest.fn(() => mockOAuth2),
+}
+
+export = oauth
diff --git a/packages/worker/package.json b/packages/worker/package.json
index a9a093e753..c925b9d5d6 100644
--- a/packages/worker/package.json
+++ b/packages/worker/package.json
@@ -71,9 +71,11 @@
},
"devDependencies": {
"@types/jest": "26.0.23",
+ "@types/jsonwebtoken": "8.5.1",
"@types/koa": "2.13.4",
"@types/koa__router": "8.0.11",
"@types/node": "14.18.20",
+ "@types/node-fetch": "2.6.1",
"@types/pouchdb": "6.4.0",
"@types/uuid": "8.3.4",
"@typescript-eslint/parser": "5.12.0",
diff --git a/packages/worker/src/api/controllers/global/auth.ts b/packages/worker/src/api/controllers/global/auth.ts
index c27fe17ee7..9065267658 100644
--- a/packages/worker/src/api/controllers/global/auth.ts
+++ b/packages/worker/src/api/controllers/global/auth.ts
@@ -1,4 +1,4 @@
-const core = require("@budibase/backend-core")
+import core from "@budibase/backend-core"
const { Configs, EmailTemplatePurpose } = require("../../../constants")
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
const { setCookie, getCookie, clearCookie, hash, platformLogout } = core.utils
diff --git a/packages/worker/src/api/routes/global/auth.js b/packages/worker/src/api/routes/global/auth.js
index 674279a6f4..2bf6bb68bf 100644
--- a/packages/worker/src/api/routes/global/auth.js
+++ b/packages/worker/src/api/routes/global/auth.js
@@ -29,11 +29,13 @@ function buildResetUpdateValidation() {
}
router
+ // PASSWORD
.post(
"/api/global/auth/:tenantId/login",
buildAuthValidation(),
authController.authenticate
)
+ .post("/api/global/auth/logout", authController.logout)
.post(
"/api/global/auth/:tenantId/reset",
buildResetValidation(),
@@ -44,36 +46,43 @@ router
buildResetUpdateValidation(),
authController.resetUpdate
)
- .post("/api/global/auth/logout", authController.logout)
+ // INIT
.post("/api/global/auth/init", authController.setInitInfo)
.get("/api/global/auth/init", authController.getInitInfo)
- .get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
+
+ // DATASOURCE - MULTI TENANT
.get(
"/api/global/auth/:tenantId/datasource/:provider",
authController.datasourcePreAuth
)
- // single tenancy endpoint
- .get("/api/global/auth/google/callback", authController.googleAuth)
- .get(
- "/api/global/auth/datasource/:provider/callback",
- authController.datasourceAuth
- )
- // multi-tenancy endpoint
- .get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
.get(
"/api/global/auth/:tenantId/datasource/:provider/callback",
authController.datasourceAuth
)
+
+ // DATASOURCE - SINGLE TENANT - DEPRECATED
+ .get(
+ "/api/global/auth/datasource/:provider/callback",
+ authController.datasourceAuth
+ )
+
+ // GOOGLE - MULTI TENANT
+ .get("/api/global/auth/:tenantId/google", authController.googlePreAuth)
+ .get("/api/global/auth/:tenantId/google/callback", authController.googleAuth)
+
+ // GOOGLE - SINGLE TENANT - DEPRECATED
+ .get("/api/global/auth/google/callback", authController.googleAuth)
+ .get("/api/admin/auth/google/callback", authController.googleAuth)
+
+ // OIDC - MULTI TENANT
.get(
"/api/global/auth/:tenantId/oidc/configs/:configId",
authController.oidcPreAuth
)
- // single tenancy endpoint
- .get("/api/global/auth/oidc/callback", authController.oidcAuth)
- // multi-tenancy endpoint
.get("/api/global/auth/:tenantId/oidc/callback", authController.oidcAuth)
- // deprecated - used by the default system before tenancy
- .get("/api/admin/auth/google/callback", authController.googleAuth)
+
+ // OIDC - SINGLE TENANT - DEPRECATED
+ .get("/api/global/auth/oidc/callback", authController.oidcAuth)
.get("/api/admin/auth/oidc/callback", authController.oidcAuth)
module.exports = router
diff --git a/packages/worker/src/api/routes/global/tests/auth.spec.ts b/packages/worker/src/api/routes/global/tests/auth.spec.ts
index 45c8a62cc7..0d47857ac1 100644
--- a/packages/worker/src/api/routes/global/tests/auth.spec.ts
+++ b/packages/worker/src/api/routes/global/tests/auth.spec.ts
@@ -3,6 +3,12 @@ import { TestConfiguration, mocks } from "../../../../tests"
const sendMailMock = mocks.email.mock()
import { events } from "@budibase/backend-core"
+const expectSetAuthCookie = (res: any) => {
+ expect(
+ res.get("Set-Cookie").find((c: string) => c.startsWith("budibase:auth"))
+ ).toBeDefined()
+}
+
describe("/api/global/auth", () => {
const config = new TestConfiguration()
@@ -18,92 +24,155 @@ describe("/api/global/auth", () => {
jest.clearAllMocks()
})
- it("should logout", async () => {
- await config.api.auth.logout()
- expect(events.auth.logout).toBeCalledTimes(1)
- })
-
- it("should be able to generate password reset email", async () => {
- const { res, code } = await config.api.auth.requestPasswordReset(
- sendMailMock
- )
- const user = await config.getUser("test@test.com")
-
- expect(res.body).toEqual({
- message: "Please check your email for a reset link.",
+ describe("password", () => {
+ describe("POST /api/global/auth/:tenantId/login", () => {
+ it("should login", () => {})
})
- expect(sendMailMock).toHaveBeenCalled()
- expect(code).toBeDefined()
- expect(events.user.passwordResetRequested).toBeCalledTimes(1)
- expect(events.user.passwordResetRequested).toBeCalledWith(user)
+ describe("POST /api/global/auth/logout", () => {
+ it("should logout", async () => {
+ await config.api.auth.logout()
+ expect(events.auth.logout).toBeCalledTimes(1)
+
+ // TODO: Verify sessions deleted
+ })
+ })
+
+ describe("POST /api/global/auth/:tenantId/reset", () => {
+ it("should generate password reset email", async () => {
+ const { res, code } = await config.api.auth.requestPasswordReset(
+ sendMailMock
+ )
+ const user = await config.getUser("test@test.com")
+
+ expect(res.body).toEqual({
+ message: "Please check your email for a reset link.",
+ })
+ expect(sendMailMock).toHaveBeenCalled()
+
+ expect(code).toBeDefined()
+ expect(events.user.passwordResetRequested).toBeCalledTimes(1)
+ expect(events.user.passwordResetRequested).toBeCalledWith(user)
+ })
+ })
+
+ describe("POST /api/global/auth/:tenantId/reset/update", () => {
+ it("should reset password", async () => {
+ const { code } = await config.api.auth.requestPasswordReset(
+ sendMailMock
+ )
+ const user = await config.getUser("test@test.com")
+ delete user.password
+
+ const res = await config.api.auth.updatePassword(code)
+
+ expect(res.body).toEqual({ message: "password reset successfully." })
+ expect(events.user.passwordReset).toBeCalledTimes(1)
+ expect(events.user.passwordReset).toBeCalledWith(user)
+
+ // TODO: Login using new password
+ })
+ })
})
- it("should allow resetting user password with code", async () => {
- const { code } = await config.api.auth.requestPasswordReset(sendMailMock)
- const user = await config.getUser("test@test.com")
- delete user.password
+ describe("init", () => {
+ describe("POST /api/global/auth/init", () => {})
- const res = await config.api.auth.updatePassword(code)
+ describe("GET /api/global/auth/init", () => {})
+ })
- expect(res.body).toEqual({ message: "password reset successfully." })
- expect(events.user.passwordReset).toBeCalledTimes(1)
- expect(events.user.passwordReset).toBeCalledWith(user)
+ describe("datasource", () => {
+ // MULTI TENANT
+
+ describe("GET /api/global/auth/:tenantId/datasource/:provider", () => {})
+
+ describe("GET /api/global/auth/:tenantId/datasource/:provider/callback", () => {})
+
+ // SINGLE TENANT
+
+ describe("GET /api/global/auth/datasource/:provider/callback", () => {})
+ })
+
+ describe("google", () => {
+ // MULTI TENANT
+
+ describe("GET /api/global/auth/:tenantId/google", () => {})
+
+ describe("GET /api/global/auth/:tenantId/google/callback", () => {})
+
+ // SINGLE TENANT
+
+ describe("GET /api/global/auth/google/callback", () => {})
+
+ describe("GET /api/admin/auth/google/callback", () => {})
})
describe("oidc", () => {
- const auth = require("@budibase/backend-core/auth")
-
- const passportSpy = jest.spyOn(auth.passport, "authenticate")
- let oidcConf
- let chosenConfig: any
- let configId: string
-
- // mock the oidc strategy implementation and return value
- let strategyFactory = jest.fn()
- let mockStrategyReturn = jest.fn()
- let mockStrategyConfig = jest.fn()
- auth.oidc.fetchStrategyConfig = mockStrategyConfig
-
- strategyFactory.mockReturnValue(mockStrategyReturn)
- auth.oidc.strategyFactory = strategyFactory
-
beforeEach(async () => {
- oidcConf = await config.saveOIDCConfig()
- chosenConfig = oidcConf.config.configs[0]
- configId = chosenConfig.uuid
- mockStrategyConfig.mockReturnValue(chosenConfig)
+ jest.clearAllMocks()
+ mockGetWellKnownConfig()
+
+ // see: __mocks__/oauth
+ // for associated mocking inside passport
})
- afterEach(() => {
- expect(strategyFactory).toBeCalledWith(chosenConfig, expect.any(Function))
- })
+ const generateOidcConfig = async () => {
+ const oidcConf = await config.saveOIDCConfig()
+ const chosenConfig = oidcConf.config.configs[0]
+ return chosenConfig.uuid
+ }
- describe("oidc configs", () => {
- it("should load strategy and delegate to passport", async () => {
- await config.api.configs.getOIDCConfig(configId)
+ const mockGetWellKnownConfig = () => {
+ mocks.fetch.mockReturnValue({
+ ok: true,
+ json: () => ({
+ issuer: "test",
+ authorization_endpoint: "http://localhost/auth",
+ token_endpoint: "http://localhost/token",
+ userinfo_endpoint: "http://localhost/userinfo",
+ }),
+ })
+ }
- expect(passportSpy).toBeCalledWith(mockStrategyReturn, {
- scope: ["profile", "email", "offline_access"],
- })
- expect(passportSpy.mock.calls.length).toBe(1)
+ // MULTI TENANT
+ describe("GET /api/global/auth/:tenantId/oidc/configs/:configId", () => {
+ it("redirects to auth provider", async () => {
+ const configId = await generateOidcConfig()
+
+ const res = await config.api.configs.getOIDCConfig(configId)
+
+ expect(res.status).toBe(302)
+ const location: string = res.get("location")
+ expect(
+ location.startsWith(
+ "http://localhost/auth?response_type=code&client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A10000%2Fapi%2Fglobal%2Fauth%2Fdefault%2Foidc%2Fcallback&scope=openid%20profile%20email%20offline_access"
+ )
+ ).toBe(true)
})
})
- describe("oidc callback", () => {
- it("should load strategy and delegate to passport", async () => {
- await config.api.configs.OIDCCallback(configId)
+ describe("GET /api/global/auth/:tenantId/oidc/callback", () => {
+ it("logs in", async () => {
+ const configId = await generateOidcConfig()
+ const preAuthRes = await config.api.configs.getOIDCConfig(configId)
- expect(passportSpy).toBeCalledWith(
- mockStrategyReturn,
- {
- successRedirect: "/",
- failureRedirect: "/error",
- },
- expect.anything()
- )
- expect(passportSpy.mock.calls.length).toBe(1)
+ const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
+
+ expect(events.auth.login).toBeCalledWith("oidc")
+ expect(events.auth.login).toBeCalledTimes(1)
+ expect(res.status).toBe(302)
+ const location: string = res.get("location")
+ expect(location).toBe("/")
+ expectSetAuthCookie(res)
})
})
+
+ // SINGLE TENANT
+
+ describe("GET /api/global/auth/oidc/callback", () => {})
+
+ describe("GET /api/global/auth/oidc/callback", () => {})
+
+ describe("GET /api/admin/auth/oidc/callback", () => {})
})
})
diff --git a/packages/worker/src/api/routes/system/tests/tenants.spec.ts b/packages/worker/src/api/routes/system/tests/tenants.spec.ts
index 8b3bcc99f5..af509b402e 100644
--- a/packages/worker/src/api/routes/system/tests/tenants.spec.ts
+++ b/packages/worker/src/api/routes/system/tests/tenants.spec.ts
@@ -1,7 +1,7 @@
import { TestConfiguration } from "../../../../tests"
import { tenancy } from "@budibase/backend-core"
-describe("/api/global/workspaces", () => {
+describe("/api/global/tenants", () => {
const config = new TestConfiguration()
beforeAll(async () => {
diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts
index 2927915284..11da7c2b03 100644
--- a/packages/worker/src/tests/TestConfiguration.ts
+++ b/packages/worker/src/tests/TestConfiguration.ts
@@ -171,8 +171,11 @@ class TestConfiguration {
}
cookieHeader(cookies: any) {
+ if (!Array.isArray(cookies)) {
+ cookies = [cookies]
+ }
return {
- Cookie: [cookies],
+ Cookie: cookies,
}
}
@@ -288,11 +291,6 @@ class TestConfiguration {
// CONFIGS - OIDC
- getOIDConfigCookie(configId: string) {
- const token = auth.jwt.sign(configId, env.JWT_SECRET)
- return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
- }
-
async saveOIDCConfig() {
await this.deleteConfig(Configs.OIDC)
const config = structures.configs.oidc()
diff --git a/packages/worker/src/tests/api/configs.ts b/packages/worker/src/tests/api/configs.ts
index 6799229f58..10413dfdd6 100644
--- a/packages/worker/src/tests/api/configs.ts
+++ b/packages/worker/src/tests/api/configs.ts
@@ -23,10 +23,20 @@ export class ConfigAPI extends TestAPI {
.expect(200)
}
- OIDCCallback = (configId: string) => {
+ OIDCCallback = (configId: string, preAuthRes: any) => {
+ const cookie = this.config.cookieHeader(preAuthRes.get("set-cookie"))
+ const setKoaSession = cookie.Cookie.find((c: string) =>
+ c.includes("koa:sess")
+ )
+ const koaSession = setKoaSession.split("=")[1] + "=="
+ const sessionContent = JSON.parse(
+ Buffer.from(koaSession, "base64").toString("utf-8")
+ )
+ const handle = sessionContent["openidconnect:localhost"].state.handle
return this.request
.get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`)
- .set(this.config.getOIDConfigCookie(configId))
+ .query({ code: "test", state: handle })
+ .set(cookie)
}
getOIDCConfig = (configId: string) => {
diff --git a/packages/worker/yarn.lock b/packages/worker/yarn.lock
index cfdba32e7a..23f54ae770 100644
--- a/packages/worker/yarn.lock
+++ b/packages/worker/yarn.lock
@@ -1284,6 +1284,13 @@
resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64"
integrity sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==
+"@types/jsonwebtoken@8.5.1":
+ version "8.5.1"
+ resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#56958cb2d80f6d74352bd2e501a018e2506a8a84"
+ integrity sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==
+ dependencies:
+ "@types/node" "*"
+
"@types/keygrip@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
@@ -1334,6 +1341,14 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
+"@types/node-fetch@2.6.1":
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975"
+ integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==
+ dependencies:
+ "@types/node" "*"
+ form-data "^3.0.0"
+
"@types/node@*":
version "17.0.41"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b"
@@ -3412,6 +3427,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
+form-data@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
+ integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
diff --git a/qa-core/src/config/internal-api/TestConfiguration/applications.ts b/qa-core/src/config/internal-api/TestConfiguration/applications.ts
index cb0558222e..13d0969854 100644
--- a/qa-core/src/config/internal-api/TestConfiguration/applications.ts
+++ b/qa-core/src/config/internal-api/TestConfiguration/applications.ts
@@ -114,8 +114,6 @@ export default class AppApi {
return [response, json]
}
-
-
async delete(appId: string): Promise<[Response, any]> {
const response = await this.api.del(`/applications/${appId}`)
const json = await response.json()
@@ -123,7 +121,11 @@ export default class AppApi {
return [response, json]
}
- async update(appId: string, oldName: string, body: any): Promise<[Response, Application]> {
+ async update(
+ appId: string,
+ oldName: string,
+ body: any
+ ): Promise<[Response, Application]> {
const response = await this.api.put(`/applications/${appId}`, { body })
const json = await response.json()
expect(response).toHaveStatusCode(200)
@@ -142,7 +144,6 @@ export default class AppApi {
const json = await response.json()
expect(response).toHaveStatusCode(200)
if (screenExists) {
-
expect(json.routes["/test"]).toBeTruthy()
} else {
expect(json.routes["/test"]).toBeUndefined()
diff --git a/qa-core/src/config/internal-api/TestConfiguration/tables.ts b/qa-core/src/config/internal-api/TestConfiguration/tables.ts
index ed0ab78cad..5b7e1648a0 100644
--- a/qa-core/src/config/internal-api/TestConfiguration/tables.ts
+++ b/qa-core/src/config/internal-api/TestConfiguration/tables.ts
@@ -46,9 +46,7 @@ export default class TablesApi {
const response = await this.api.del(`/tables/${id}/${revId}`)
const json = await response.json()
expect(response).toHaveStatusCode(200)
- expect(json.message).toEqual(
- `Table ${id} deleted.`
- )
+ expect(json.message).toEqual(`Table ${id} deleted.`)
return [response, json]
}
}
diff --git a/qa-core/src/config/internal-api/fixtures/applications.ts b/qa-core/src/config/internal-api/fixtures/applications.ts
index abdd674577..200aa9abff 100644
--- a/qa-core/src/config/internal-api/fixtures/applications.ts
+++ b/qa-core/src/config/internal-api/fixtures/applications.ts
@@ -1,7 +1,6 @@
import generator from "../../generator"
import { Application } from "@budibase/server/api/controllers/public/mapping/types"
-
const generate = (
overrides: Partial
= {}
): Partial => ({
diff --git a/qa-core/src/tests/internal-api/applications/applications.spec.ts b/qa-core/src/tests/internal-api/applications/applications.spec.ts
index 4b9b66ec65..4b3208ee10 100644
--- a/qa-core/src/tests/internal-api/applications/applications.spec.ts
+++ b/qa-core/src/tests/internal-api/applications/applications.spec.ts
@@ -48,7 +48,8 @@ describe("Internal API - Application creation, update, publish and delete", () =
})
config.applications.api.appId = app.appId
- const [appPackageResponse, appPackageJson] = await config.applications.getAppPackage(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)
@@ -72,7 +73,6 @@ describe("Internal API - Application creation, update, publish and delete", () =
config.applications.api.appId = db.getProdAppID(app.appId)
await config.applications.canRender()
-
// unpublish app
await config.applications.unpublish(app.appId)
})
@@ -109,22 +109,16 @@ describe("Internal API - Application creation, update, publish and delete", () =
config.applications.api.appId = app.appId
- await config.applications.update(
- app.appId,
- app.name,
- {
- name: generator.word(),
- }
- )
+ await config.applications.update(app.appId, app.name, {
+ name: generator.word(),
+ })
})
it("POST - Revert Changes without changes", async () => {
const app = await config.applications.create(generateApp())
config.applications.api.appId = app.appId
- await config.applications.revertUnpublished(
- app.appId
- )
+ await config.applications.revertUnpublished(app.appId)
})
it("POST - Revert Changes", async () => {
@@ -134,20 +128,14 @@ describe("Internal API - Application creation, update, publish and delete", () =
// publish app
await config.applications.publish(app.url)
-
// Change/add component to the app
- await config.screen.create(
- generateScreen("BASIC")
- )
+ await config.screen.create(generateScreen("BASIC"))
// // Revert the app to published state
- await config.applications.revertPublished(
- app.appId
- )
+ await config.applications.revertPublished(app.appId)
// Check screen is removed
await config.applications.getRoutes()
-
})
it("DELETE - Delete an application", async () => {
@@ -155,5 +143,4 @@ describe("Internal API - Application creation, update, publish and delete", () =
await config.applications.delete(app.appId)
})
-
})
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 2dc7962914..218d71cb0d 100644
--- a/qa-core/src/tests/internal-api/screens/screens.spec.ts
+++ b/qa-core/src/tests/internal-api/screens/screens.spec.ts
@@ -38,9 +38,7 @@ describe("Internal API - /screens endpoints", () => {
// Create Screen
appConfig.applications.api.appId = app.appId
- await config.screen.create(
- generateScreen("BASIC")
- )
+ await config.screen.create(generateScreen("BASIC"))
// Check screen exists
await appConfig.applications.getRoutes(true)
@@ -58,6 +56,5 @@ describe("Internal API - /screens endpoints", () => {
// Delete Screen
await config.screen.delete(screen._id!, screen._rev!)
-
})
})
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 69ad0fed7b..4f9e4299cf 100644
--- a/qa-core/src/tests/internal-api/tables/tables.spec.ts
+++ b/qa-core/src/tests/internal-api/tables/tables.spec.ts
@@ -3,93 +3,87 @@ import { Application } from "@budibase/server/api/controllers/public/mapping/typ
import InternalAPIClient from "../../../config/internal-api/TestConfiguration/InternalAPIClient"
import generator from "../../../config/generator"
import {
- generateTable,
- generateNewColumnForTable,
+ generateTable,
+ generateNewColumnForTable,
} from "../../../config/internal-api/fixtures/table"
import { generateNewRowForTable } from "../../../config/internal-api/fixtures/rows"
describe("Internal API - Application creation, update, publish and delete", () => {
- const api = new InternalAPIClient()
- const config = new TestConfiguration(api)
+ const api = new InternalAPIClient()
+ const config = new TestConfiguration(api)
- beforeAll(async () => {
- await config.beforeAll()
+ beforeAll(async () => {
+ await config.beforeAll()
+ })
+
+ afterAll(async () => {
+ await config.afterAll()
+ })
+
+ async function createAppFromTemplate() {
+ return config.applications.create({
+ name: generator.word(),
+ url: `/${generator.word()}`,
+ useTemplate: "true",
+ templateName: "Near Miss Register",
+ templateKey: "app/near-miss-register",
+ templateFile: undefined,
})
+ }
- afterAll(async () => {
- await config.afterAll()
- })
+ it("Operations on Tables", async () => {
+ // create the app
+ const appName = generator.word()
+ const app = await createAppFromTemplate()
+ config.applications.api.appId = app.appId
- async function createAppFromTemplate() {
- return config.applications.create({
- name: generator.word(),
- url: `/${generator.word()}`,
- useTemplate: "true",
- templateName: "Near Miss Register",
- templateKey: "app/near-miss-register",
- templateFile: undefined,
- })
+ // Get current tables: expect 2 in this template
+ await config.tables.getAll(2)
+
+ // Add new table
+ const [createdTableResponse, createdTableData] = await config.tables.save(
+ generateTable()
+ )
+
+ //Table was added
+ await config.tables.getAll(3)
+
+ //Get information about the table
+ await config.tables.getTableById(createdTableData._id)
+
+ //Add Column to table
+ const newColumn = generateNewColumnForTable(createdTableData)
+ const [addColumnResponse, addColumnData] = await config.tables.save(
+ newColumn,
+ true
+ )
+
+ //Add Row to table
+ const newRow = generateNewRowForTable(addColumnData._id)
+ await config.rows.add(addColumnData._id, newRow)
+
+ //Get Row from table
+ const [getRowResponse, getRowData] = await config.rows.getAll(
+ addColumnData._id
+ )
+
+ //Delete Row from table
+ const rowToDelete = {
+ rows: [getRowData[0]],
}
+ const [deleteRowResponse, deleteRowData] = await config.rows.delete(
+ addColumnData._id,
+ rowToDelete
+ )
+ expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
- it("Operations on Tables", async () => {
- // create the app
- const appName = generator.word()
- const app = await createAppFromTemplate()
- config.applications.api.appId = app.appId
+ //Delete the table
+ const [deleteTableResponse, deleteTable] = await config.tables.delete(
+ addColumnData._id,
+ addColumnData._rev
+ )
- // Get current tables: expect 2 in this template
- await config.tables.getAll(2)
-
- // Add new table
- const [createdTableResponse, createdTableData] = await config.tables.save(
- generateTable()
- )
-
- //Table was added
- await config.tables.getAll(3)
-
- //Get information about the table
- await config.tables.getTableById(
- createdTableData._id
- )
-
- //Add Column to table
- const newColumn = generateNewColumnForTable(createdTableData)
- const [addColumnResponse, addColumnData] = await config.tables.save(
- newColumn,
- true
- )
-
- //Add Row to table
- const newRow = generateNewRowForTable(addColumnData._id)
- await config.rows.add(
- addColumnData._id,
- newRow
- )
-
- //Get Row from table
- const [getRowResponse, getRowData] = await config.rows.getAll(
- addColumnData._id
- )
-
- //Delete Row from table
- const rowToDelete = {
- rows: [getRowData[0]],
- }
- const [deleteRowResponse, deleteRowData] = await config.rows.delete(
- addColumnData._id,
- rowToDelete
- )
- expect(deleteRowData[0]._id).toEqual(getRowData[0]._id)
-
- //Delete the table
- const [deleteTableResponse, deleteTable] = await config.tables.delete(
- addColumnData._id,
- addColumnData._rev
- )
-
-
- //Table was deleted
- await config.tables.getAll(2)
- })
+ //Table was deleted
+ await config.tables.getAll(2)
+ })
})