diff --git a/packages/backend-core/src/middleware/matchers.ts b/packages/backend-core/src/middleware/matchers.ts index e40a9a61b3..efbdec2dbe 100644 --- a/packages/backend-core/src/middleware/matchers.ts +++ b/packages/backend-core/src/middleware/matchers.ts @@ -21,7 +21,6 @@ export const buildMatcherRegex = ( const suffix = match.endsWith("/") ? "/" : "" const pattern = "/.*" + suffix route = route.replace(match, pattern) - console.log(route) } } diff --git a/packages/backend-core/src/tenancy/tenancy.ts b/packages/backend-core/src/tenancy/tenancy.ts index 66d170d3f8..99955ca321 100644 --- a/packages/backend-core/src/tenancy/tenancy.ts +++ b/packages/backend-core/src/tenancy/tenancy.ts @@ -225,6 +225,7 @@ export const getTenantIDFromCtx = ( } } + // path if (isAllowed(TenantResolutionStrategy.PATH)) { // params - have to parse manually due to koa-router not run yet const match = ctx.matched.find( diff --git a/packages/backend-core/tests/utilities/structures/common.ts b/packages/backend-core/tests/utilities/structures/common.ts index 51ae220254..05b879f36b 100644 --- a/packages/backend-core/tests/utilities/structures/common.ts +++ b/packages/backend-core/tests/utilities/structures/common.ts @@ -1 +1,7 @@ +import { v4 as uuid } from "uuid" + export { v4 as uuid } from "uuid" + +export const email = () => { + return `${uuid()}@test.com` +} diff --git a/packages/types/src/api/web/user.ts b/packages/types/src/api/web/user.ts index 98ffcdf360..0ebe4ccce8 100644 --- a/packages/types/src/api/web/user.ts +++ b/packages/types/src/api/web/user.ts @@ -51,3 +51,9 @@ export interface SearchUsersRequest { appId?: string userIds?: string[] } + +export interface CreateAdminUserRequest { + email: string + password: string + tenantId: string +} diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index ea1df5b45a..7edb1b710a 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -5,6 +5,7 @@ import { BulkUserRequest, BulkUserResponse, CloudAccount, + CreateAdminUserRequest, InviteUserRequest, InviteUsersRequest, SearchUsersRequest, @@ -67,7 +68,8 @@ const parseBooleanParam = (param: any) => { } export const adminUser = async (ctx: any) => { - const { email, password, tenantId } = ctx.request.body + const { email, password, tenantId } = ctx.request + .body as CreateAdminUserRequest await tenancy.doInTenant(tenantId, async () => { // account portal sends a pre-hashed password - honour param to prevent double hashing const hashPassword = parseBooleanParam(ctx.request.query.hashPassword) diff --git a/packages/worker/src/api/controllers/system/tenants.ts b/packages/worker/src/api/controllers/system/tenants.ts index 8fd1446bcb..6916049534 100644 --- a/packages/worker/src/api/controllers/system/tenants.ts +++ b/packages/worker/src/api/controllers/system/tenants.ts @@ -11,8 +11,8 @@ const _delete = async (ctx: BBContext) => { } try { - await deprovisioning.deleteTenant(tenantId) await quotas.bustCache() + await deprovisioning.deleteTenant(tenantId) ctx.status = 204 } catch (err) { ctx.log.error(err) 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 69fa1b223c..45c8a62cc7 100644 --- a/packages/worker/src/api/routes/global/tests/auth.spec.ts +++ b/packages/worker/src/api/routes/global/tests/auth.spec.ts @@ -1,11 +1,10 @@ jest.mock("nodemailer") -import { TestConfiguration, mocks, API } from "../../../../tests" +import { TestConfiguration, mocks } from "../../../../tests" const sendMailMock = mocks.email.mock() import { events } from "@budibase/backend-core" describe("/api/global/auth", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -20,12 +19,14 @@ describe("/api/global/auth", () => { }) it("should logout", async () => { - await api.auth.logout() + 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 api.auth.requestPasswordReset(sendMailMock) + const { res, code } = await config.api.auth.requestPasswordReset( + sendMailMock + ) const user = await config.getUser("test@test.com") expect(res.body).toEqual({ @@ -39,11 +40,11 @@ describe("/api/global/auth", () => { }) it("should allow resetting user password with code", async () => { - const { code } = await api.auth.requestPasswordReset(sendMailMock) + const { code } = await config.api.auth.requestPasswordReset(sendMailMock) const user = await config.getUser("test@test.com") delete user.password - const res = await api.auth.updatePassword(code) + const res = await config.api.auth.updatePassword(code) expect(res.body).toEqual({ message: "password reset successfully." }) expect(events.user.passwordReset).toBeCalledTimes(1) @@ -80,7 +81,7 @@ describe("/api/global/auth", () => { describe("oidc configs", () => { it("should load strategy and delegate to passport", async () => { - await api.configs.getOIDCConfig(configId) + await config.api.configs.getOIDCConfig(configId) expect(passportSpy).toBeCalledWith(mockStrategyReturn, { scope: ["profile", "email", "offline_access"], @@ -91,7 +92,7 @@ describe("/api/global/auth", () => { describe("oidc callback", () => { it("should load strategy and delegate to passport", async () => { - await api.configs.OIDCCallback(configId) + await config.api.configs.OIDCCallback(configId) expect(passportSpy).toBeCalledWith( mockStrategyReturn, diff --git a/packages/worker/src/api/routes/global/tests/configs.spec.ts b/packages/worker/src/api/routes/global/tests/configs.spec.ts index 99376988da..be4308c7b2 100644 --- a/packages/worker/src/api/routes/global/tests/configs.spec.ts +++ b/packages/worker/src/api/routes/global/tests/configs.spec.ts @@ -1,12 +1,11 @@ // mock the email system jest.mock("nodemailer") -import { TestConfiguration, structures, mocks, API } from "../../../../tests" +import { TestConfiguration, structures, mocks } from "../../../../tests" mocks.email.mock() import { Configs, events } from "@budibase/backend-core" describe("configs", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -28,7 +27,7 @@ describe("configs", () => { _rev, } - const res = await api.configs.saveConfig(data) + const res = await config.api.configs.saveConfig(data) return { ...data, @@ -266,7 +265,7 @@ describe("configs", () => { it("should return the correct checklist status based on the state of the budibase installation", async () => { await config.saveSmtpConfig() - const res = await api.configs.getConfigChecklist() + const res = await config.api.configs.getConfigChecklist() const checklist = res.body expect(checklist.apps.checked).toBeFalsy() diff --git a/packages/worker/src/api/routes/global/tests/email.spec.ts b/packages/worker/src/api/routes/global/tests/email.spec.ts index 608f4094f8..9e65cda3c5 100644 --- a/packages/worker/src/api/routes/global/tests/email.spec.ts +++ b/packages/worker/src/api/routes/global/tests/email.spec.ts @@ -1,11 +1,10 @@ jest.mock("nodemailer") -import { TestConfiguration, mocks, API } from "../../../../tests" +import { TestConfiguration, mocks } from "../../../../tests" const sendMailMock = mocks.email.mock() import { EmailTemplatePurpose } from "../../../../constants" describe("/api/global/email", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -20,7 +19,9 @@ describe("/api/global/email", () => { await config.saveSmtpConfig() await config.saveSettingsConfig() - const res = await api.emails.sendEmail(EmailTemplatePurpose.INVITATION) + const res = await config.api.emails.sendEmail( + EmailTemplatePurpose.INVITATION + ) expect(res.body.message).toBeDefined() expect(sendMailMock).toHaveBeenCalled() diff --git a/packages/worker/src/api/routes/global/tests/license.spec.ts b/packages/worker/src/api/routes/global/tests/license.spec.ts index a1566730ea..b25b41adb9 100644 --- a/packages/worker/src/api/routes/global/tests/license.spec.ts +++ b/packages/worker/src/api/routes/global/tests/license.spec.ts @@ -1,8 +1,9 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" + +// TODO describe("/api/global/license", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -16,7 +17,9 @@ describe("/api/global/license", () => { jest.clearAllMocks() }) - describe("POST /api/global/license/activate", () => {}) + describe("POST /api/global/license/activate", () => { + it("activates license", () => {}) + }) describe("POST /api/global/license/refresh", () => {}) diff --git a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts index 135367e0d8..1c180be75d 100644 --- a/packages/worker/src/api/routes/global/tests/realEmail.spec.ts +++ b/packages/worker/src/api/routes/global/tests/realEmail.spec.ts @@ -1,4 +1,4 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" import { EmailTemplatePurpose } from "../../../../constants" const nodemailer = require("nodemailer") const fetch = require("node-fetch") @@ -8,7 +8,6 @@ jest.setTimeout(30000) describe("/api/global/email", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -35,7 +34,7 @@ describe("/api/global/email", () => { await Promise.race([config.saveEtherealSmtpConfig(), timeout()]) await Promise.race([config.saveSettingsConfig(), timeout()]) - const res = await api.emails.sendEmail(purpose).timeout(20000) + const res = await config.api.emails.sendEmail(purpose).timeout(20000) // ethereal hiccup, can't test right now if (res.status >= 300) { return diff --git a/packages/worker/src/api/routes/global/tests/roles.spec.ts b/packages/worker/src/api/routes/global/tests/roles.spec.ts index c9d6d085a5..516c3433ab 100644 --- a/packages/worker/src/api/routes/global/tests/roles.spec.ts +++ b/packages/worker/src/api/routes/global/tests/roles.spec.ts @@ -1,8 +1,9 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" + +// TODO describe("/api/global/roles", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -16,7 +17,9 @@ describe("/api/global/roles", () => { jest.clearAllMocks() }) - describe("GET /api/global/roles", () => {}) + describe("GET /api/global/roles", () => { + it("retrieves roles", () => {}) + }) describe("GET /api/global/roles/:appId", () => {}) diff --git a/packages/worker/src/api/routes/global/tests/self.spec.ts b/packages/worker/src/api/routes/global/tests/self.spec.ts index 5640bab3ce..d253a7f24e 100644 --- a/packages/worker/src/api/routes/global/tests/self.spec.ts +++ b/packages/worker/src/api/routes/global/tests/self.spec.ts @@ -1,10 +1,9 @@ jest.mock("nodemailer") -import { TestConfiguration, API, mocks } from "../../../../tests" +import { TestConfiguration, mocks } from "../../../../tests" import { events } from "@budibase/backend-core" describe("/api/global/self", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -24,7 +23,7 @@ describe("/api/global/self", () => { await config.createSession(user) delete user.password - const res = await api.self.updateSelf(user) + const res = await config.api.self.updateSelf(user) const dbUser = await config.getUser(user.email) user._rev = dbUser._rev @@ -40,7 +39,7 @@ describe("/api/global/self", () => { await config.createSession(user) user.password = "newPassword" - const res = await api.self.updateSelf(user) + const res = await config.api.self.updateSelf(user) const dbUser = await config.getUser(user.email) user._rev = dbUser._rev diff --git a/packages/worker/src/api/routes/global/tests/templates.spec.ts b/packages/worker/src/api/routes/global/tests/templates.spec.ts index 8986639637..d1c296643d 100644 --- a/packages/worker/src/api/routes/global/tests/templates.spec.ts +++ b/packages/worker/src/api/routes/global/tests/templates.spec.ts @@ -1,8 +1,9 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" + +// TODO describe("/api/global/template", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -16,7 +17,9 @@ describe("/api/global/template", () => { jest.clearAllMocks() }) - describe("GET /api/global/template/definitions", () => {}) + describe("GET /api/global/template/definitions", () => { + it("retrieves definitions", () => {}) + }) describe("POST /api/global/template", () => {}) diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 218bc60800..3165cba315 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -6,14 +6,12 @@ import { mocks, structures, TENANT_1, - API, } from "../../../../tests" const sendMailMock = mocks.email.mock() import { events, tenancy } from "@budibase/backend-core" describe("/api/global/users", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -30,7 +28,10 @@ describe("/api/global/users", () => { describe("invite", () => { it("should be able to generate an invitation", async () => { const email = structures.users.newEmail() - const { code, res } = await api.users.sendUserInvite(sendMailMock, email) + const { code, res } = await config.api.users.sendUserInvite( + sendMailMock, + email + ) expect(res.body).toEqual({ message: "Invitation has been sent." }) expect(sendMailMock).toHaveBeenCalled() @@ -39,7 +40,7 @@ describe("/api/global/users", () => { }) it("should not be able to generate an invitation for existing user", async () => { - const { code, res } = await api.users.sendUserInvite( + const { code, res } = await config.api.users.sendUserInvite( sendMailMock, config.defaultUser!.email, 400 @@ -53,9 +54,12 @@ describe("/api/global/users", () => { it("should be able to create new user from invite", async () => { const email = structures.users.newEmail() - const { code } = await api.users.sendUserInvite(sendMailMock, email) + const { code } = await config.api.users.sendUserInvite( + sendMailMock, + email + ) - const res = await api.users.acceptInvite(code) + const res = await config.api.users.acceptInvite(code) expect(res.body._id).toBeDefined() const user = await config.getUser(email) @@ -74,7 +78,7 @@ describe("/api/global/users", () => { }) const request = [newUserInvite(), newUserInvite()] - const res = await api.users.sendMultiUserInvite(request) + const res = await config.api.users.sendMultiUserInvite(request) const body = res.body as InviteUsersResponse expect(body.successful.length).toBe(2) @@ -86,7 +90,7 @@ describe("/api/global/users", () => { it("should not be able to generate an invitation for existing user", async () => { const request = [{ email: config.defaultUser!.email, userInfo: {} }] - const res = await api.users.sendMultiUserInvite(request) + const res = await config.api.users.sendMultiUserInvite(request) const body = res.body as InviteUsersResponse expect(body.successful.length).toBe(0) @@ -102,7 +106,7 @@ describe("/api/global/users", () => { const user = await config.createUser() jest.clearAllMocks() - const response = await api.users.bulkCreateUsers([user]) + const response = await config.api.users.bulkCreateUsers([user]) expect(response.created?.successful.length).toBe(0) expect(response.created?.unsuccessful.length).toBe(1) @@ -115,7 +119,7 @@ describe("/api/global/users", () => { jest.resetAllMocks() await tenancy.doInTenant(TENANT_1, async () => { - const response = await api.users.bulkCreateUsers([user]) + const response = await config.api.users.bulkCreateUsers([user]) expect(response.created?.successful.length).toBe(0) expect(response.created?.unsuccessful.length).toBe(1) @@ -126,11 +130,11 @@ describe("/api/global/users", () => { it("should ignore accounts using the same email", async () => { const account = structures.accounts.account() - const resp = await api.accounts.saveMetadata(account) + const resp = await config.api.accounts.saveMetadata(account) const user = structures.users.user({ email: resp.email }) jest.clearAllMocks() - const response = await api.users.bulkCreateUsers([user]) + const response = await config.api.users.bulkCreateUsers([user]) expect(response.created?.successful.length).toBe(0) expect(response.created?.unsuccessful.length).toBe(1) @@ -143,7 +147,11 @@ describe("/api/global/users", () => { const admin = structures.users.adminUser() const user = structures.users.user() - const response = await api.users.bulkCreateUsers([builder, admin, user]) + const response = await config.api.users.bulkCreateUsers([ + builder, + admin, + user, + ]) expect(response.created?.successful.length).toBe(3) expect(response.created?.successful[0].email).toBe(builder.email) @@ -160,7 +168,7 @@ describe("/api/global/users", () => { it("should be able to create a basic user", async () => { const user = structures.users.user() - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).toBeCalledTimes(1) expect(events.user.updated).not.toBeCalled() @@ -171,7 +179,7 @@ describe("/api/global/users", () => { it("should be able to create an admin user", async () => { const user = structures.users.adminUser() - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).toBeCalledTimes(1) expect(events.user.updated).not.toBeCalled() @@ -182,7 +190,7 @@ describe("/api/global/users", () => { it("should be able to create a builder user", async () => { const user = structures.users.builderUser() - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).toBeCalledTimes(1) expect(events.user.updated).not.toBeCalled() @@ -197,7 +205,7 @@ describe("/api/global/users", () => { app_456: "role2", } - await api.users.saveUser(user) + await config.api.users.saveUser(user) const savedUser = await config.getUser(user.email) expect(events.user.created).toBeCalledTimes(1) @@ -213,7 +221,7 @@ describe("/api/global/users", () => { delete user._id delete user._rev - const response = await api.users.saveUser(user, 400) + const response = await config.api.users.saveUser(user, 400) expect(response.body.message).toBe(`Unavailable`) expect(events.user.created).toBeCalledTimes(0) @@ -225,7 +233,7 @@ describe("/api/global/users", () => { await tenancy.doInTenant(TENANT_1, async () => { delete user._id - const response = await api.users.saveUser(user, 400) + const response = await config.api.users.saveUser(user, 400) expect(response.body.message).toBe(`Unavailable`) expect(events.user.created).toBeCalledTimes(0) @@ -237,7 +245,7 @@ describe("/api/global/users", () => { const account = structures.accounts.cloudAccount() mocks.accounts.getAccount.mockReturnValueOnce(account) - const response = await api.users.saveUser(user, 400) + const response = await config.api.users.saveUser(user, 400) expect(response.body.message).toBe(`Unavailable`) expect(events.user.created).toBeCalledTimes(0) @@ -245,20 +253,20 @@ describe("/api/global/users", () => { it("should not be able to create a user with the same email and different casing", async () => { const user = structures.users.user() - await api.users.saveUser(user) + await config.api.users.saveUser(user) user.email = user.email.toUpperCase() - await api.users.saveUser(user, 400) + await config.api.users.saveUser(user, 400) expect(events.user.created).toBeCalledTimes(1) }) it("should not be able to bulk create a user with the same email and different casing", async () => { const user = structures.users.user() - await api.users.saveUser(user) + await config.api.users.saveUser(user) user.email = user.email.toUpperCase() - await api.users.bulkCreateUsers([user]) + await config.api.users.bulkCreateUsers([user]) expect(events.user.created).toBeCalledTimes(1) }) @@ -269,7 +277,7 @@ describe("/api/global/users", () => { const user = await config.createUser() jest.clearAllMocks() - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -284,7 +292,7 @@ describe("/api/global/users", () => { user.forceResetPassword = true user.password = "tempPassword" - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -297,7 +305,7 @@ describe("/api/global/users", () => { const user = await config.createUser() jest.clearAllMocks() - await api.users.saveUser(structures.users.adminUser(user)) + await config.api.users.saveUser(structures.users.adminUser(user)) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -309,7 +317,7 @@ describe("/api/global/users", () => { const user = await config.createUser() jest.clearAllMocks() - await api.users.saveUser(structures.users.builderUser(user)) + await config.api.users.saveUser(structures.users.builderUser(user)) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -323,7 +331,7 @@ describe("/api/global/users", () => { user.admin!.global = false user.builder!.global = false - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -336,7 +344,7 @@ describe("/api/global/users", () => { jest.clearAllMocks() user.builder!.global = false - await api.users.saveUser(user) + await config.api.users.saveUser(user) expect(events.user.created).not.toBeCalled() expect(events.user.updated).toBeCalledTimes(1) @@ -352,7 +360,7 @@ describe("/api/global/users", () => { app_456: "role2", } - await api.users.saveUser(user) + await config.api.users.saveUser(user) const savedUser = await config.getUser(user.email) expect(events.user.created).not.toBeCalled() @@ -372,7 +380,7 @@ describe("/api/global/users", () => { jest.clearAllMocks() user.roles = {} - await api.users.saveUser(user) + await config.api.users.saveUser(user) const savedUser = await config.getUser(user.email) expect(events.user.created).not.toBeCalled() @@ -395,7 +403,7 @@ describe("/api/global/users", () => { app_456: "role2-edit", } - await api.users.saveUser(user) + await config.api.users.saveUser(user) const savedUser = await config.getUser(user.email) expect(events.user.created).not.toBeCalled() @@ -411,7 +419,7 @@ describe("/api/global/users", () => { const user = await config.createUser(structures.users.user({ email })) user.email = "new@test.com" - const response = await api.users.saveUser(user, 400) + const response = await config.api.users.saveUser(user, 400) const dbUser = await config.getUser(email) user.email = email @@ -424,7 +432,7 @@ describe("/api/global/users", () => { it("should not be able to bulk delete current user", async () => { const user = await config.defaultUser! - const response = await api.users.bulkDeleteUsers([user._id!], 400) + const response = await config.api.users.bulkDeleteUsers([user._id!], 400) expect(response.message).toBe("Unable to delete self.") expect(events.user.deleted).not.toBeCalled() @@ -436,7 +444,7 @@ describe("/api/global/users", () => { account.budibaseUserId = user._id! mocks.accounts.getAccountByTenantId.mockReturnValue(account) - const response = await api.users.bulkDeleteUsers([user._id!]) + const response = await config.api.users.bulkDeleteUsers([user._id!]) expect(response.deleted?.successful.length).toBe(0) expect(response.deleted?.unsuccessful.length).toBe(1) @@ -454,7 +462,7 @@ describe("/api/global/users", () => { const builder = structures.users.builderUser() const admin = structures.users.adminUser() const user = structures.users.user() - const createdUsers = await api.users.bulkCreateUsers([ + const createdUsers = await config.api.users.bulkCreateUsers([ builder, admin, user, @@ -463,7 +471,7 @@ describe("/api/global/users", () => { const toDelete = createdUsers.created?.successful.map( u => u._id! ) as string[] - const response = await api.users.bulkDeleteUsers(toDelete) + const response = await config.api.users.bulkDeleteUsers(toDelete) expect(response.deleted?.successful.length).toBe(3) expect(response.deleted?.unsuccessful.length).toBe(0) @@ -478,7 +486,7 @@ describe("/api/global/users", () => { const user = await config.createUser() jest.clearAllMocks() - await api.users.deleteUser(user._id!) + await config.api.users.deleteUser(user._id!) expect(events.user.deleted).toBeCalledTimes(1) expect(events.user.permissionBuilderRemoved).not.toBeCalled() @@ -489,7 +497,7 @@ describe("/api/global/users", () => { const user = await config.createUser(structures.users.adminUser()) jest.clearAllMocks() - await api.users.deleteUser(user._id!) + await config.api.users.deleteUser(user._id!) expect(events.user.deleted).toBeCalledTimes(1) expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) @@ -500,7 +508,7 @@ describe("/api/global/users", () => { const user = await config.createUser(structures.users.builderUser()) jest.clearAllMocks() - await api.users.deleteUser(user._id!) + await config.api.users.deleteUser(user._id!) expect(events.user.deleted).toBeCalledTimes(1) expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) @@ -512,7 +520,7 @@ describe("/api/global/users", () => { const account = structures.accounts.cloudAccount() mocks.accounts.getAccount.mockReturnValueOnce(account) - const response = await api.users.deleteUser(user._id!, 400) + const response = await config.api.users.deleteUser(user._id!, 400) expect(response.body.message).toBe("Account holder cannot be deleted") }) @@ -523,7 +531,7 @@ describe("/api/global/users", () => { account.email = user.email mocks.accounts.getAccount.mockReturnValueOnce(account) - const response = await api.users.deleteUser(user._id!, 400) + const response = await config.api.users.deleteUser(user._id!, 400) expect(response.body.message).toBe("Unable to delete self.") }) diff --git a/packages/worker/src/api/routes/global/tests/workspaces.spec.ts b/packages/worker/src/api/routes/global/tests/workspaces.spec.ts index f7ab1d9a7a..1a30c6525c 100644 --- a/packages/worker/src/api/routes/global/tests/workspaces.spec.ts +++ b/packages/worker/src/api/routes/global/tests/workspaces.spec.ts @@ -1,8 +1,9 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" + +// TODO describe("/api/global/workspaces", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -16,7 +17,9 @@ describe("/api/global/workspaces", () => { jest.clearAllMocks() }) - describe("GET /api/global/workspaces", () => {}) + describe("GET /api/global/workspaces", () => { + it("retrieves workspaces", () => {}) + }) describe("DELETE /api/global/workspaces/:id", () => {}) diff --git a/packages/worker/src/api/routes/system/tests/accounts.spec.ts b/packages/worker/src/api/routes/system/tests/accounts.spec.ts index f977d22cd9..fd54dd2b0a 100644 --- a/packages/worker/src/api/routes/system/tests/accounts.spec.ts +++ b/packages/worker/src/api/routes/system/tests/accounts.spec.ts @@ -1,10 +1,9 @@ import sdk from "../../../../sdk" -import { TestConfiguration, structures, API } from "../../../../tests" +import { TestConfiguration, structures } from "../../../../tests" import { v4 as uuid } from "uuid" describe("accounts", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -23,7 +22,7 @@ describe("accounts", () => { it("saves account metadata", async () => { let account = structures.accounts.account() - const response = await api.accounts.saveMetadata(account) + const response = await config.api.accounts.saveMetadata(account) const id = sdk.accounts.formatAccountMetadataId(account.accountId) const metadata = await sdk.accounts.getMetadata(id) @@ -34,9 +33,9 @@ describe("accounts", () => { describe("destroyMetadata", () => { it("destroys account metadata", async () => { const account = structures.accounts.account() - await api.accounts.saveMetadata(account) + await config.api.accounts.saveMetadata(account) - await api.accounts.destroyMetadata(account.accountId) + await config.api.accounts.destroyMetadata(account.accountId) const deleted = await sdk.accounts.getMetadata(account.accountId) expect(deleted).toBe(undefined) @@ -45,7 +44,7 @@ describe("accounts", () => { it("destroys account metadata that does not exist", async () => { const id = uuid() - const response = await api.accounts.destroyMetadata(id) + const response = await config.api.accounts.destroyMetadata(id) expect(response.status).toBe(204) }) diff --git a/packages/worker/src/api/routes/system/tests/environment.spec.ts b/packages/worker/src/api/routes/system/tests/environment.spec.ts index 5cc3b0c6f1..f18ae1ba91 100644 --- a/packages/worker/src/api/routes/system/tests/environment.spec.ts +++ b/packages/worker/src/api/routes/system/tests/environment.spec.ts @@ -1,8 +1,7 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" describe("/api/system/environment", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -18,7 +17,7 @@ describe("/api/system/environment", () => { describe("GET /api/system/environment", () => { it("returns the expected environment", async () => { - const env = await api.environment.getEnvironment() + const env = await config.api.environment.getEnvironment() expect(env.body).toEqual({ cloud: true, disableAccountPortal: false, diff --git a/packages/worker/src/api/routes/system/tests/migrations.spec.ts b/packages/worker/src/api/routes/system/tests/migrations.spec.ts index 0e3883f9ef..304a64761e 100644 --- a/packages/worker/src/api/routes/system/tests/migrations.spec.ts +++ b/packages/worker/src/api/routes/system/tests/migrations.spec.ts @@ -1,6 +1,6 @@ const migrateFn = jest.fn() -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" jest.mock("../../../../migrations", () => { return { @@ -11,7 +11,6 @@ jest.mock("../../../../migrations", () => { describe("/api/system/migrations", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -27,7 +26,7 @@ describe("/api/system/migrations", () => { describe("POST /api/system/migrations/run", () => { it("fails with no internal api key", async () => { - const res = await api.migrations.runMigrations({ + const res = await config.api.migrations.runMigrations({ headers: {}, status: 403, }) @@ -36,7 +35,7 @@ describe("/api/system/migrations", () => { }) it("runs migrations", async () => { - const res = await api.migrations.runMigrations() + const res = await config.api.migrations.runMigrations() expect(res.text).toBe("OK") expect(migrateFn).toBeCalledTimes(1) }) @@ -44,7 +43,7 @@ describe("/api/system/migrations", () => { describe("DELETE /api/system/migrations/definitions", () => { it("fails with no internal api key", async () => { - const res = await api.migrations.getMigrationDefinitions({ + const res = await config.api.migrations.getMigrationDefinitions({ headers: {}, status: 403, }) @@ -52,7 +51,7 @@ describe("/api/system/migrations", () => { }) it("returns definitions", async () => { - const res = await api.migrations.getMigrationDefinitions() + const res = await config.api.migrations.getMigrationDefinitions() expect(res.body).toEqual([ { name: "global_info_sync_users", diff --git a/packages/worker/src/api/routes/system/tests/restore.spec.ts b/packages/worker/src/api/routes/system/tests/restore.spec.ts index 1da9389608..4dd973270f 100644 --- a/packages/worker/src/api/routes/system/tests/restore.spec.ts +++ b/packages/worker/src/api/routes/system/tests/restore.spec.ts @@ -1,8 +1,7 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" describe("/api/system/restore", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -18,7 +17,7 @@ describe("/api/system/restore", () => { describe("POST /api/global/restore", () => { it("doesn't allow restore in cloud", async () => { - const res = await api.restore.restored({ status: 405 }) + const res = await config.api.restore.restored({ status: 405 }) expect(res.body).toEqual({ message: "This operation is not allowed in cloud.", status: 405, @@ -27,7 +26,7 @@ describe("/api/system/restore", () => { it("restores in self host", async () => { config.modeSelf() - const res = await api.restore.restored() + const res = await config.api.restore.restored() expect(res.body).toEqual({ message: "System prepared after restore.", }) diff --git a/packages/worker/src/api/routes/system/tests/status.spec.ts b/packages/worker/src/api/routes/system/tests/status.spec.ts index 98308556d1..afd3f8ac46 100644 --- a/packages/worker/src/api/routes/system/tests/status.spec.ts +++ b/packages/worker/src/api/routes/system/tests/status.spec.ts @@ -1,10 +1,9 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" import { accounts } from "@budibase/backend-core" import { mocks } from "@budibase/backend-core/tests" describe("/api/system/status", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -21,7 +20,7 @@ describe("/api/system/status", () => { describe("GET /api/system/status", () => { it("returns status in self host", async () => { config.modeSelf() - const res = await api.status.getStatus() + const res = await config.api.status.getStatus() expect(res.body).toEqual({ health: { passing: true, @@ -40,7 +39,7 @@ describe("/api/system/status", () => { mocks.accounts.getStatus.mockReturnValueOnce(value) - const res = await api.status.getStatus() + const res = await config.api.status.getStatus() expect(accounts.getStatus).toBeCalledTimes(1) expect(res.body).toEqual(value) 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 0491d43e8c..8b3bcc99f5 100644 --- a/packages/worker/src/api/routes/system/tests/tenants.spec.ts +++ b/packages/worker/src/api/routes/system/tests/tenants.spec.ts @@ -1,8 +1,8 @@ -import { TestConfiguration, API } from "../../../../tests" +import { TestConfiguration } from "../../../../tests" +import { tenancy } from "@budibase/backend-core" describe("/api/global/workspaces", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -19,14 +19,43 @@ describe("/api/global/workspaces", () => { describe("DELETE /api/system/tenants/:tenantId", () => { it("allows deleting the current tenant", async () => { const user = await config.createTenant() - await config.createSession(user) - const res = await api.tenants.delete(user.tenantId, { + + await config.api.tenants.delete(user.tenantId, { headers: config.authHeaders(user), }) }) - it("rejects deleting another tenant", () => {}) + it("rejects deleting another tenant", async () => { + const user1 = await config.createTenant() + // create a second user in another tenant + const user2 = await config.createTenant() - it("requires admin", () => {}) + const status = 403 + const res = await config.api.tenants.delete(user1.tenantId, { + status, + headers: config.authHeaders(user2), + }) + + expect(res.body).toEqual({ + message: "Tenant ID does not match current user", + status, + }) + }) + + it("rejects non-admin", async () => { + const user1 = await config.createTenant() + // create an internal non-admin user + const user2 = await tenancy.doInTenant(user1.tenantId, () => { + return config.createUser() + }) + await config.createSession(user2) + + const res = await config.api.tenants.delete(user1.tenantId, { + status: 403, + headers: config.authHeaders(user2), + }) + + expect(res.body).toEqual(config.adminOnlyResponse()) + }) }) }) diff --git a/packages/worker/src/middleware/tests/tenancy.spec.ts b/packages/worker/src/middleware/tests/tenancy.spec.ts index 04de41b9cf..72c00fb6fb 100644 --- a/packages/worker/src/middleware/tests/tenancy.spec.ts +++ b/packages/worker/src/middleware/tests/tenancy.spec.ts @@ -1,9 +1,8 @@ -import { TestConfiguration, API, structures } from "../../tests" +import { TestConfiguration, structures } from "../../tests" import { constants } from "@budibase/backend-core" describe("tenancy middleware", () => { const config = new TestConfiguration() - const api = new API(config) beforeAll(async () => { await config.beforeAll() @@ -20,7 +19,7 @@ describe("tenancy middleware", () => { it("should get tenant id from user", async () => { const user = await config.createTenant() await config.createSession(user) - const res = await api.self.getSelf(user) + const res = await config.api.self.getSelf(user) expect(res.headers[constants.Headers.TENANT_ID]).toBe(user.tenantId) }) diff --git a/packages/worker/src/tests/TestConfiguration.ts b/packages/worker/src/tests/TestConfiguration.ts index 31305066da..ca02bda7a5 100644 --- a/packages/worker/src/tests/TestConfiguration.ts +++ b/packages/worker/src/tests/TestConfiguration.ts @@ -2,7 +2,6 @@ import "./mocks" import dbConfig from "../db" dbConfig.init() import env from "../environment" -import { env as coreEnv } from "@budibase/backend-core" import controllers from "./controllers" const supertest = require("supertest") import { Configs } from "../constants" @@ -14,9 +13,11 @@ import { sessions, auth, constants, + env as coreEnv, } from "@budibase/backend-core" import structures, { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures" import { CreateUserResponse, User, AuthToken } from "@budibase/types" +import API from "./api" enum Mode { CLOUD = "cloud", @@ -26,6 +27,7 @@ enum Mode { class TestConfiguration { server: any request: any + api: API defaultUser?: User tenant1User?: User @@ -47,6 +49,8 @@ class TestConfiguration { // we need the request for logging in, involves cookies, hard to fake this.request = supertest(this.server) } + + this.api = new API(this) } getRequest() { @@ -119,14 +123,23 @@ class TestConfiguration { // TENANCY - createTenant = async (tenantId?: string): Promise => { + createTenant = async (): Promise => { // create user / new tenant - if (!tenantId) { - tenantId = structures.uuid() - } - return tenancy.doInTenant(tenantId, async () => { - return this.createUser() + const res = await this.api.users.createAdminUser() + + // return the created user + const userRes = await this.api.users.getUser(res.userId, { + headers: { + ...this.internalAPIHeaders(), + [constants.Headers.TENANT_ID]: res.tenantId, + }, }) + + // create a session for the new user + const user = userRes.body + await this.createSession(user) + + return user } getTenantId() { @@ -137,30 +150,24 @@ class TestConfiguration { } } - // USER / AUTH + // AUTH - async createDefaultUser() { - const user = structures.users.adminUser({ - email: "test@test.com", - password: "test", + async _createSession({ + userId, + tenantId, + }: { + userId: string + tenantId: string + }) { + await sessions.createASession(userId!, { + sessionId: "sessionid", + tenantId: tenantId, + csrfToken: CSRF_TOKEN, }) - this.defaultUser = await this.createUser(user) - } - - async createTenant1User() { - const user = structures.users.adminUser({ - email: "tenant1@test.com", - password: "test", - }) - this.tenant1User = await this.createUser(user) } async createSession(user: User) { - await sessions.createASession(user._id!, { - sessionId: "sessionid", - tenantId: user.tenantId, - csrfToken: CSRF_TOKEN, - }) + return this._createSession({ userId: user._id!, tenantId: user.tenantId }) } cookieHeader(cookies: any) { @@ -198,6 +205,28 @@ class TestConfiguration { return { [constants.Headers.API_KEY]: env.INTERNAL_API_KEY } } + adminOnlyResponse = () => { + return { message: "Admin user only endpoint.", status: 403 } + } + + // USERS + + async createDefaultUser() { + const user = structures.users.adminUser({ + email: "test@test.com", + password: "test", + }) + this.defaultUser = await this.createUser(user) + } + + async createTenant1User() { + const user = structures.users.adminUser({ + email: "tenant1@test.com", + password: "test", + }) + this.tenant1User = await this.createUser(user) + } + async getUser(email: string): Promise { return tenancy.doInTenant(this.getTenantId(), () => { return users.getGlobalUserByEmail(email) diff --git a/packages/worker/src/tests/api/accounts.ts b/packages/worker/src/tests/api/accounts.ts index fe6bf31192..bc6d055b77 100644 --- a/packages/worker/src/tests/api/accounts.ts +++ b/packages/worker/src/tests/api/accounts.ts @@ -1,20 +1,17 @@ import { Account, AccountMetadata } from "@budibase/types" import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class AccountAPI { - config: TestConfiguration - request: any - +export class AccountAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } saveMetadata = async (account: Account) => { const res = await this.request .put(`/api/system/accounts/${account.accountId}/metadata`) .send(account) - .set(this.config.defaultHeaders()) + .set(this.config.internalAPIHeaders()) .expect("Content-Type", /json/) .expect(200) return res.body as AccountMetadata @@ -23,6 +20,6 @@ export class AccountAPI { destroyMetadata = (accountId: string) => { return this.request .del(`/api/system/accounts/${accountId}/metadata`) - .set(this.config.defaultHeaders()) + .set(this.config.internalAPIHeaders()) } } diff --git a/packages/worker/src/tests/api/auth.ts b/packages/worker/src/tests/api/auth.ts index 204ae9f5dd..dda50976bd 100644 --- a/packages/worker/src/tests/api/auth.ts +++ b/packages/worker/src/tests/api/auth.ts @@ -1,12 +1,9 @@ import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class AuthAPI { - config: TestConfiguration - request: any - +export class AuthAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } updatePassword = (code: string) => { diff --git a/packages/worker/src/tests/api/configs.ts b/packages/worker/src/tests/api/configs.ts index 3a3c433fa0..6799229f58 100644 --- a/packages/worker/src/tests/api/configs.ts +++ b/packages/worker/src/tests/api/configs.ts @@ -1,12 +1,9 @@ import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class ConfigAPI { - config: TestConfiguration - request: any - +export class ConfigAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } getConfigChecklist = () => { diff --git a/packages/worker/src/tests/api/email.ts b/packages/worker/src/tests/api/email.ts index ea026c22ac..ba7c7dbec0 100644 --- a/packages/worker/src/tests/api/email.ts +++ b/packages/worker/src/tests/api/email.ts @@ -1,12 +1,9 @@ import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class EmailAPI { - config: TestConfiguration - request: any - +export class EmailAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } sendEmail = (purpose: string) => { diff --git a/packages/worker/src/tests/api/environment.ts b/packages/worker/src/tests/api/environment.ts index 9847c2e369..d9f82c5f0d 100644 --- a/packages/worker/src/tests/api/environment.ts +++ b/packages/worker/src/tests/api/environment.ts @@ -1,12 +1,9 @@ import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class EnvironmentAPI { - config: TestConfiguration - request: any - +export class EnvironmentAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } getEnvironment = () => { diff --git a/packages/worker/src/tests/api/self.ts b/packages/worker/src/tests/api/self.ts index da634e0db3..dcc6c1a98b 100644 --- a/packages/worker/src/tests/api/self.ts +++ b/packages/worker/src/tests/api/self.ts @@ -1,13 +1,10 @@ import TestConfiguration from "../TestConfiguration" import { User } from "@budibase/types" +import { TestAPI } from "./base" -export class SelfAPI { - config: TestConfiguration - request: any - +export class SelfAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } updateSelf = (user: User) => { diff --git a/packages/worker/src/tests/api/status.ts b/packages/worker/src/tests/api/status.ts index c42060631f..5b0f77efc6 100644 --- a/packages/worker/src/tests/api/status.ts +++ b/packages/worker/src/tests/api/status.ts @@ -1,12 +1,9 @@ import TestConfiguration from "../TestConfiguration" +import { TestAPI } from "./base" -export class StatusAPI { - config: TestConfiguration - request: any - +export class StatusAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } getStatus = () => { diff --git a/packages/worker/src/tests/api/tenants.ts b/packages/worker/src/tests/api/tenants.ts index d4d47a6e4f..b28b55697f 100644 --- a/packages/worker/src/tests/api/tenants.ts +++ b/packages/worker/src/tests/api/tenants.ts @@ -10,6 +10,6 @@ export class TenantAPI extends TestAPI { return this.request .delete(`/api/system/tenants/${tenantId}`) .set(opts?.headers) - .expect(opts?.status ? opts.status : 200) + .expect(opts?.status ? opts.status : 204) } } diff --git a/packages/worker/src/tests/api/users.ts b/packages/worker/src/tests/api/users.ts index 3677bfffc6..c9c5e33403 100644 --- a/packages/worker/src/tests/api/users.ts +++ b/packages/worker/src/tests/api/users.ts @@ -3,16 +3,16 @@ import { BulkUserRequest, InviteUsersRequest, User, + CreateAdminUserRequest, } from "@budibase/types" +import * as structures from "../structures" +import { generator } from "@budibase/backend-core/tests" import TestConfiguration from "../TestConfiguration" +import { TestAPI, TestAPIOpts } from "./base" -export class UserAPI { - config: TestConfiguration - request: any - +export class UserAPI extends TestAPI { constructor(config: TestConfiguration) { - this.config = config - this.request = config.request + super(config) } // INVITE @@ -91,6 +91,30 @@ export class UserAPI { // USER + createAdminUser = async ( + request?: CreateAdminUserRequest, + opts?: TestAPIOpts + ) => { + if (!request) { + request = { + email: structures.email(), + password: generator.string(), + tenantId: structures.uuid(), + } + } + const res = await this.request + .post(`/api/global/users/init`) + .send(request) + .set(this.config.internalAPIHeaders()) + .expect("Content-Type", /json/) + .expect(opts?.status ? opts.status : 200) + + return { + ...request, + userId: res.body._id, + } + } + saveUser = (user: User, status?: number) => { return this.request .post(`/api/global/users`) @@ -107,4 +131,12 @@ export class UserAPI { .expect("Content-Type", /json/) .expect(status ? status : 200) } + + getUser = (userId: string, opts?: TestAPIOpts) => { + return this.request + .get(`/api/global/users/${userId}`) + .set(opts?.headers ? opts.headers : this.config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(opts?.status ? opts.status : 200) + } }