mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
78 changed files with 2154 additions and 1156 deletions
@ -0,0 +1,7 @@ |
|||
export const getAccount = jest.fn() |
|||
export const getAccountByTenantId = jest.fn() |
|||
|
|||
jest.mock("../../../src/cloud/accounts", () => ({ |
|||
getAccount, |
|||
getAccountByTenantId, |
|||
})) |
|||
@ -1,2 +0,0 @@ |
|||
exports.MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
|||
exports.MOCK_DATE_TIMESTAMP = 1577836800000 |
|||
@ -0,0 +1,2 @@ |
|||
export const MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
|||
export const MOCK_DATE_TIMESTAMP = 1577836800000 |
|||
@ -1,9 +0,0 @@ |
|||
const posthog = require("./posthog") |
|||
const events = require("./events") |
|||
const date = require("./date") |
|||
|
|||
module.exports = { |
|||
posthog, |
|||
date, |
|||
events, |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
import "./posthog" |
|||
import "./events" |
|||
export * as accounts from "./accounts" |
|||
export * as date from "./date" |
|||
@ -0,0 +1,73 @@ |
|||
<script> |
|||
import { Body, ModalContent, Table } from "@budibase/bbui" |
|||
import { onMount } from "svelte" |
|||
|
|||
export let userData |
|||
export let deleteUsersResponse |
|||
|
|||
let successCount |
|||
let failureCount |
|||
let title |
|||
let unsuccessfulUsers |
|||
let message |
|||
|
|||
const setTitle = () => { |
|||
if (successCount) { |
|||
title = `${successCount} users deleted` |
|||
} else { |
|||
title = "Oops!" |
|||
} |
|||
} |
|||
|
|||
const setMessage = () => { |
|||
if (successCount) { |
|||
message = "However there was a problem deleting some users." |
|||
} else { |
|||
message = "There was a problem deleting some users." |
|||
} |
|||
} |
|||
|
|||
const setUsers = () => { |
|||
unsuccessfulUsers = deleteUsersResponse.unsuccessful.map(user => { |
|||
return { |
|||
email: user.email, |
|||
reason: user.reason, |
|||
} |
|||
}) |
|||
} |
|||
|
|||
onMount(() => { |
|||
successCount = deleteUsersResponse.successful.length |
|||
failureCount = deleteUsersResponse.unsuccessful.length |
|||
setTitle() |
|||
setMessage() |
|||
setUsers() |
|||
}) |
|||
|
|||
const schema = { |
|||
email: {}, |
|||
reason: {}, |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
size="M" |
|||
{title} |
|||
confirmText="Close" |
|||
showCloseIcon={false} |
|||
showCancelButton={false} |
|||
> |
|||
<Body size="XS"> |
|||
{message} |
|||
</Body> |
|||
<Table |
|||
{schema} |
|||
data={unsuccessfulUsers} |
|||
allowEditColumns={false} |
|||
allowEditRows={false} |
|||
allowSelectRows={false} |
|||
/> |
|||
</ModalContent> |
|||
|
|||
<style> |
|||
</style> |
|||
@ -0,0 +1,5 @@ |
|||
export interface APIError { |
|||
message: string |
|||
status: number |
|||
error?: any |
|||
} |
|||
@ -1 +1,3 @@ |
|||
export * from "./analytics" |
|||
export * from "./user" |
|||
export * from "./errors" |
|||
|
|||
@ -0,0 +1,31 @@ |
|||
import { User } from "../../documents" |
|||
|
|||
export interface CreateUserResponse { |
|||
_id: string |
|||
_rev: string |
|||
email: string |
|||
} |
|||
|
|||
export interface BulkCreateUsersRequest { |
|||
users: User[] |
|||
groups: any[] |
|||
} |
|||
|
|||
export interface UserDetails { |
|||
_id: string |
|||
email: string |
|||
} |
|||
|
|||
export interface BulkCreateUsersResponse { |
|||
successful: UserDetails[] |
|||
unsuccessful: { email: string; reason: string }[] |
|||
} |
|||
|
|||
export interface BulkDeleteUsersRequest { |
|||
userIds: string[] |
|||
} |
|||
|
|||
export interface BulkDeleteUsersResponse { |
|||
successful: UserDetails[] |
|||
unsuccessful: { _id: string; email: string; reason: string }[] |
|||
} |
|||
@ -1,19 +1,20 @@ |
|||
import { Document } from "../document" |
|||
import { User } from "./user" |
|||
|
|||
export interface UserGroup extends Document { |
|||
name: string |
|||
icon: string |
|||
color: string |
|||
users: groupUser[] |
|||
users: GroupUser[] |
|||
apps: string[] |
|||
roles: UserGroupRoles |
|||
createdAt?: number |
|||
} |
|||
|
|||
export interface groupUser { |
|||
export interface GroupUser { |
|||
_id: string |
|||
email: string[] |
|||
email: string |
|||
} |
|||
|
|||
export interface UserGroupRoles { |
|||
[key: string]: string |
|||
} |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
import { Document } from "../document" |
|||
|
|||
export interface AccountMetadata extends Document { |
|||
email: string |
|||
} |
|||
@ -1 +1,3 @@ |
|||
export * from "./info" |
|||
export * from "./users" |
|||
export * from "./accounts" |
|||
|
|||
@ -0,0 +1,9 @@ |
|||
import { Document } from "../document" |
|||
|
|||
/** |
|||
* doc id is user email |
|||
*/ |
|||
export interface PlatformUserByEmail extends Document { |
|||
tenantId: string |
|||
userId: string |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
export interface RowValue { |
|||
rev: string |
|||
deleted: boolean |
|||
} |
|||
|
|||
export interface RowResponse<T> { |
|||
id: string |
|||
key: string |
|||
error: string |
|||
value: RowValue |
|||
doc: T |
|||
} |
|||
|
|||
export interface AllDocsResponse<T> { |
|||
offset: number |
|||
total_rows: number |
|||
rows: RowResponse<T>[] |
|||
} |
|||
|
|||
export type BulkDocsResponse = BulkDocResponse[] |
|||
|
|||
interface BulkDocResponse { |
|||
ok: boolean |
|||
id: string |
|||
rev: string |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
export interface AuthToken { |
|||
userId: string |
|||
tenantId: string |
|||
sessionId: string |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// get the JWT secret etc
|
|||
require("../../src/environment") |
|||
require("@budibase/backend-core").init() |
|||
const { |
|||
getProdAppID, |
|||
generateGlobalUserID, |
|||
} = require("@budibase/backend-core/db") |
|||
const { doInTenant, getGlobalDB } = require("@budibase/backend-core/tenancy") |
|||
const users = require("../../src/sdk/users") |
|||
const { publicApiUserFix } = require("../../src/utilities/users") |
|||
const { hash } = require("@budibase/backend-core/utils") |
|||
|
|||
const USER_LOAD_NUMBER = 10000 |
|||
const BATCH_SIZE = 200 |
|||
const PASSWORD = "test" |
|||
const TENANT_ID = "default" |
|||
|
|||
const APP_ID = process.argv[2] |
|||
|
|||
const words = [ |
|||
"test", |
|||
"testing", |
|||
"budi", |
|||
"mail", |
|||
"age", |
|||
"risk", |
|||
"load", |
|||
"uno", |
|||
"arm", |
|||
"leg", |
|||
"pen", |
|||
"glass", |
|||
"box", |
|||
"chicken", |
|||
"bottle", |
|||
] |
|||
|
|||
if (!APP_ID) { |
|||
console.error("Must supply app ID as first CLI option!") |
|||
process.exit(-1) |
|||
} |
|||
|
|||
const WORD_1 = words[Math.floor(Math.random() * words.length)] |
|||
const WORD_2 = words[Math.floor(Math.random() * words.length)] |
|||
let HASHED_PASSWORD |
|||
|
|||
function generateUser(count) { |
|||
return { |
|||
_id: generateGlobalUserID(), |
|||
password: HASHED_PASSWORD, |
|||
email: `${WORD_1}${count}@${WORD_2}.com`, |
|||
roles: { |
|||
[getProdAppID(APP_ID)]: "BASIC", |
|||
}, |
|||
status: "active", |
|||
forceResetPassword: false, |
|||
firstName: "John", |
|||
lastName: "Smith", |
|||
} |
|||
} |
|||
|
|||
async function run() { |
|||
HASHED_PASSWORD = await hash(PASSWORD) |
|||
return doInTenant(TENANT_ID, async () => { |
|||
const db = getGlobalDB() |
|||
for (let i = 0; i < USER_LOAD_NUMBER; i += BATCH_SIZE) { |
|||
let userSavePromises = [] |
|||
for (let j = 0; j < BATCH_SIZE; j++) { |
|||
// like the public API
|
|||
const ctx = publicApiUserFix({ |
|||
request: { |
|||
body: generateUser(i + j), |
|||
}, |
|||
}) |
|||
userSavePromises.push( |
|||
users.save(ctx.request.body, { |
|||
hashPassword: false, |
|||
requirePassword: true, |
|||
bulkCreate: true, |
|||
}) |
|||
) |
|||
} |
|||
const allUsers = await Promise.all(userSavePromises) |
|||
await db.bulkDocs(allUsers) |
|||
console.log(`${i + BATCH_SIZE} users have been created.`) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
run() |
|||
.then(() => { |
|||
console.log(`Generated ${USER_LOAD_NUMBER} users!`) |
|||
}) |
|||
.catch(err => { |
|||
console.error("Failed for reason: ", err) |
|||
process.exit(-1) |
|||
}) |
|||
@ -0,0 +1,21 @@ |
|||
import { Account, AccountMetadata } from "@budibase/types" |
|||
import { accounts } from "../../../sdk" |
|||
|
|||
export const save = async (ctx: any) => { |
|||
const account = ctx.request.body as Account |
|||
let metadata: AccountMetadata = { |
|||
_id: accounts.formatAccountMetadataId(account.accountId), |
|||
email: account.email, |
|||
} |
|||
|
|||
metadata = await accounts.saveMetadata(metadata) |
|||
|
|||
ctx.body = metadata |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
export const destroy = async (ctx: any) => { |
|||
const accountId = accounts.formatAccountMetadataId(ctx.params.accountId) |
|||
await accounts.destroyMetadata(accountId) |
|||
ctx.status = 204 |
|||
} |
|||
@ -0,0 +1,464 @@ |
|||
jest.mock("nodemailer") |
|||
import { |
|||
TestConfiguration, |
|||
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() |
|||
}) |
|||
|
|||
afterAll(async () => { |
|||
await config.afterAll() |
|||
}) |
|||
|
|||
beforeEach(() => { |
|||
jest.clearAllMocks() |
|||
}) |
|||
|
|||
describe("invite", () => { |
|||
it("should be able to generate an invitation", async () => { |
|||
const { code, res } = await api.users.sendUserInvite(sendMailMock) |
|||
|
|||
expect(res.body).toEqual({ message: "Invitation has been sent." }) |
|||
expect(sendMailMock).toHaveBeenCalled() |
|||
expect(code).toBeDefined() |
|||
expect(events.user.invited).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to create new user from invite", async () => { |
|||
const { code } = await api.users.sendUserInvite(sendMailMock) |
|||
|
|||
const res = await api.users.acceptInvite(code) |
|||
|
|||
expect(res.body._id).toBeDefined() |
|||
const user = await config.getUser("invite@test.com") |
|||
expect(user).toBeDefined() |
|||
expect(user._id).toEqual(res.body._id) |
|||
expect(events.user.inviteAccepted).toBeCalledTimes(1) |
|||
expect(events.user.inviteAccepted).toBeCalledWith(user) |
|||
}) |
|||
}) |
|||
|
|||
describe("bulkCreate", () => { |
|||
it("should ignore users existing in the same tenant", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
const response = await api.users.bulkCreateUsers([user]) |
|||
|
|||
expect(response.successful.length).toBe(0) |
|||
expect(response.unsuccessful.length).toBe(1) |
|||
expect(response.unsuccessful[0].email).toBe(user.email) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
|
|||
it("should ignore users existing in other tenants", async () => { |
|||
const user = await config.createUser() |
|||
jest.resetAllMocks() |
|||
|
|||
await tenancy.doInTenant(TENANT_1, async () => { |
|||
const response = await api.users.bulkCreateUsers([user]) |
|||
|
|||
expect(response.successful.length).toBe(0) |
|||
expect(response.unsuccessful.length).toBe(1) |
|||
expect(response.unsuccessful[0].email).toBe(user.email) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
}) |
|||
|
|||
it("should ignore accounts using the same email", async () => { |
|||
const account = structures.accounts.account() |
|||
const resp = await api.accounts.saveMetadata(account) |
|||
const user = structures.users.user({ email: resp.email }) |
|||
jest.clearAllMocks() |
|||
|
|||
const response = await api.users.bulkCreateUsers([user]) |
|||
|
|||
expect(response.successful.length).toBe(0) |
|||
expect(response.unsuccessful.length).toBe(1) |
|||
expect(response.unsuccessful[0].email).toBe(user.email) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
|
|||
it("should be able to bulkCreate users", async () => { |
|||
const builder = structures.users.builderUser() |
|||
const admin = structures.users.adminUser() |
|||
const user = structures.users.user() |
|||
|
|||
const response = await api.users.bulkCreateUsers([builder, admin, user]) |
|||
|
|||
expect(response.successful.length).toBe(3) |
|||
expect(response.successful[0].email).toBe(builder.email) |
|||
expect(response.successful[1].email).toBe(admin.email) |
|||
expect(response.successful[2].email).toBe(user.email) |
|||
expect(response.unsuccessful.length).toBe(0) |
|||
expect(events.user.created).toBeCalledTimes(3) |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(2) |
|||
}) |
|||
}) |
|||
|
|||
describe("create", () => { |
|||
it("should be able to create a basic user", async () => { |
|||
const user = structures.users.user() |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to create an admin user", async () => { |
|||
const user = structures.users.adminUser() |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to create a builder user", async () => { |
|||
const user = structures.users.builderUser() |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to assign app roles", async () => { |
|||
const user = structures.users.user() |
|||
user.roles = { |
|||
app_123: "role1", |
|||
app_456: "role2", |
|||
} |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
const savedUser = await config.getUser(user.email) |
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.role.assigned).toBeCalledTimes(2) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
|
|||
it("should not be able to create user that exists in same tenant", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
delete user._id |
|||
delete user._rev |
|||
|
|||
const response = await api.users.saveUser(user, 400) |
|||
|
|||
expect(response.body.message).toBe(`Unavailable`) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
|
|||
it("should not be able to create user that exists in other tenant", async () => { |
|||
const user = await config.createUser() |
|||
jest.resetAllMocks() |
|||
|
|||
await tenancy.doInTenant(TENANT_1, async () => { |
|||
delete user._id |
|||
const response = await api.users.saveUser(user, 400) |
|||
|
|||
expect(response.body.message).toBe(`Unavailable`) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
}) |
|||
|
|||
it("should not be able to create user with the same email as an account", async () => { |
|||
const user = structures.users.user() |
|||
const account = structures.accounts.cloudAccount() |
|||
mocks.accounts.getAccount.mockReturnValueOnce(account) |
|||
|
|||
const response = await api.users.saveUser(user, 400) |
|||
|
|||
expect(response.body.message).toBe(`Unavailable`) |
|||
expect(events.user.created).toBeCalledTimes(0) |
|||
}) |
|||
}) |
|||
|
|||
describe("update", () => { |
|||
it("should be able to update a basic user", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
expect(events.user.passwordForceReset).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to force reset password", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
user.forceResetPassword = true |
|||
user.password = "tempPassword" |
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
expect(events.user.passwordForceReset).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to update a basic user to an admin user", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.saveUser(structures.users.adminUser(user)) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to update a basic user to a builder user", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.saveUser(structures.users.builderUser(user)) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to update an admin user to a basic user", async () => { |
|||
const user = await config.createUser(structures.users.adminUser()) |
|||
jest.clearAllMocks() |
|||
user.admin!.global = false |
|||
user.builder!.global = false |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to update an builder user to a basic user", async () => { |
|||
const user = await config.createUser(structures.users.builderUser()) |
|||
jest.clearAllMocks() |
|||
user.builder!.global = false |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to assign app roles", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
user.roles = { |
|||
app_123: "role1", |
|||
app_456: "role2", |
|||
} |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
const savedUser = await config.getUser(user.email) |
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.assigned).toBeCalledTimes(2) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
|
|||
it("should be able to unassign app roles", async () => { |
|||
let user = structures.users.user() |
|||
user.roles = { |
|||
app_123: "role1", |
|||
app_456: "role2", |
|||
} |
|||
user = await config.createUser(user) |
|||
jest.clearAllMocks() |
|||
user.roles = {} |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
const savedUser = await config.getUser(user.email) |
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledTimes(2) |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
|
|||
it("should be able to update existing app roles", async () => { |
|||
let user = structures.users.user() |
|||
user.roles = { |
|||
app_123: "role1", |
|||
app_456: "role2", |
|||
} |
|||
user = await config.createUser(user) |
|||
jest.clearAllMocks() |
|||
user.roles = { |
|||
app_123: "role1", |
|||
app_456: "role2-edit", |
|||
} |
|||
|
|||
await api.users.saveUser(user) |
|||
|
|||
const savedUser = await config.getUser(user.email) |
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2") |
|||
expect(events.role.assigned).toBeCalledTimes(1) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2-edit") |
|||
}) |
|||
|
|||
it("should not be able to update email address", async () => { |
|||
const email = "email@test.com" |
|||
const user = await config.createUser(structures.users.user({ email })) |
|||
user.email = "new@test.com" |
|||
|
|||
const response = await api.users.saveUser(user, 400) |
|||
|
|||
const dbUser = await config.getUser(email) |
|||
user.email = email |
|||
expect(user).toStrictEqual(dbUser) |
|||
expect(response.body.message).toBe("Email address cannot be changed") |
|||
}) |
|||
}) |
|||
|
|||
describe("bulkDelete", () => { |
|||
it("should not be able to bulkDelete current user", async () => { |
|||
const user = await config.defaultUser! |
|||
const request = { userIds: [user._id!] } |
|||
|
|||
const response = await api.users.bulkDeleteUsers(request, 400) |
|||
|
|||
expect(response.body.message).toBe("Unable to delete self.") |
|||
expect(events.user.deleted).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should not be able to bulkDelete account owner", async () => { |
|||
const user = await config.createUser() |
|||
const account = structures.accounts.cloudAccount() |
|||
account.budibaseUserId = user._id! |
|||
mocks.accounts.getAccountByTenantId.mockReturnValue(account) |
|||
|
|||
const request = { userIds: [user._id!] } |
|||
|
|||
const response = await api.users.bulkDeleteUsers(request) |
|||
|
|||
expect(response.body.successful.length).toBe(0) |
|||
expect(response.body.unsuccessful.length).toBe(1) |
|||
expect(response.body.unsuccessful[0].reason).toBe( |
|||
"Account holder cannot be deleted" |
|||
) |
|||
expect(response.body.unsuccessful[0]._id).toBe(user._id) |
|||
expect(events.user.deleted).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to bulk delete users", async () => { |
|||
const account = structures.accounts.cloudAccount() |
|||
mocks.accounts.getAccountByTenantId.mockReturnValue(account) |
|||
|
|||
const builder = structures.users.builderUser() |
|||
const admin = structures.users.adminUser() |
|||
const user = structures.users.user() |
|||
const createdUsers = await api.users.bulkCreateUsers([ |
|||
builder, |
|||
admin, |
|||
user, |
|||
]) |
|||
const request = { userIds: createdUsers.successful.map(u => u._id!) } |
|||
|
|||
const response = await api.users.bulkDeleteUsers(request) |
|||
|
|||
expect(response.body.successful.length).toBe(3) |
|||
expect(response.body.unsuccessful.length).toBe(0) |
|||
expect(events.user.deleted).toBeCalledTimes(3) |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(2) |
|||
}) |
|||
}) |
|||
|
|||
describe("destroy", () => { |
|||
it("should be able to destroy a basic user", async () => { |
|||
const user = await config.createUser() |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.deleteUser(user._id!) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).not.toBeCalled() |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to destroy an admin user", async () => { |
|||
const user = await config.createUser(structures.users.adminUser()) |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.deleteUser(user._id!) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to destroy a builder user", async () => { |
|||
const user = await config.createUser(structures.users.builderUser()) |
|||
jest.clearAllMocks() |
|||
|
|||
await api.users.deleteUser(user._id!) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should not be able to destroy account owner", async () => { |
|||
const user = await config.createUser() |
|||
const account = structures.accounts.cloudAccount() |
|||
mocks.accounts.getAccount.mockReturnValueOnce(account) |
|||
|
|||
const response = await api.users.deleteUser(user._id!, 400) |
|||
|
|||
expect(response.body.message).toBe("Account holder cannot be deleted") |
|||
}) |
|||
|
|||
it("should not be able to destroy account owner as account owner", async () => { |
|||
const user = await config.defaultUser! |
|||
const account = structures.accounts.cloudAccount() |
|||
account.email = user.email |
|||
mocks.accounts.getAccount.mockReturnValueOnce(account) |
|||
|
|||
const response = await api.users.deleteUser(user._id!, 400) |
|||
|
|||
expect(response.body.message).toBe("Unable to delete self.") |
|||
}) |
|||
}) |
|||
}) |
|||
@ -0,0 +1,19 @@ |
|||
import Router from "@koa/router" |
|||
import * as controller from "../../controllers/system/accounts" |
|||
import { middleware } from "@budibase/backend-core" |
|||
|
|||
const router = new Router() |
|||
|
|||
router |
|||
.put( |
|||
"/api/system/accounts/:accountId/metadata", |
|||
middleware.internalApi, |
|||
controller.save |
|||
) |
|||
.delete( |
|||
"/api/system/accounts/:accountId/metadata", |
|||
middleware.internalApi, |
|||
controller.destroy |
|||
) |
|||
|
|||
export = router |
|||
@ -0,0 +1,57 @@ |
|||
import { accounts } from "../../../../sdk" |
|||
import { TestConfiguration, structures, API } from "../../../../tests" |
|||
import { v4 as uuid } from "uuid" |
|||
|
|||
describe("accounts", () => { |
|||
const config = new TestConfiguration() |
|||
const api = new API(config) |
|||
|
|||
beforeAll(async () => { |
|||
await config.beforeAll() |
|||
}) |
|||
|
|||
afterAll(async () => { |
|||
await config.afterAll() |
|||
}) |
|||
|
|||
beforeEach(() => { |
|||
jest.clearAllMocks() |
|||
}) |
|||
|
|||
describe("metadata", () => { |
|||
describe("saveMetadata", () => { |
|||
it("saves account metadata", async () => { |
|||
let account = structures.accounts.account() |
|||
|
|||
const response = await api.accounts.saveMetadata(account) |
|||
|
|||
const id = accounts.formatAccountMetadataId(account.accountId) |
|||
const metadata = await accounts.getMetadata(id) |
|||
expect(response).toStrictEqual(metadata) |
|||
}) |
|||
}) |
|||
|
|||
describe("destroyMetadata", () => { |
|||
it("destroys account metadata", async () => { |
|||
const account = structures.accounts.account() |
|||
await api.accounts.saveMetadata(account) |
|||
|
|||
await api.accounts.destroyMetadata(account.accountId) |
|||
|
|||
const deleted = await accounts.getMetadata(account.accountId) |
|||
expect(deleted).toBe(undefined) |
|||
}) |
|||
|
|||
it("destroys account metadata that does not exist", async () => { |
|||
const id = uuid() |
|||
|
|||
const response = await api.accounts.destroyMetadata(id) |
|||
|
|||
expect(response.status).toBe(404) |
|||
expect(response.body.message).toBe( |
|||
`id=${accounts.formatAccountMetadataId(id)} does not exist` |
|||
) |
|||
}) |
|||
}) |
|||
}) |
|||
}) |
|||
@ -1,390 +0,0 @@ |
|||
jest.mock("nodemailer") |
|||
const { config, request, mocks, structures } = require("../../../tests") |
|||
const sendMailMock = mocks.email.mock() |
|||
const { events } = require("@budibase/backend-core") |
|||
describe("/api/global/users", () => { |
|||
|
|||
beforeAll(async () => { |
|||
await config.beforeAll() |
|||
}) |
|||
|
|||
afterAll(async () => { |
|||
await config.afterAll() |
|||
}) |
|||
|
|||
const sendUserInvite = async () => { |
|||
await config.saveSmtpConfig() |
|||
await config.saveSettingsConfig() |
|||
const res = await request |
|||
.post(`/api/global/users/invite`) |
|||
.send({ |
|||
email: "invite@test.com", |
|||
}) |
|||
.set(config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
|
|||
const emailCall = sendMailMock.mock.calls[0][0] |
|||
// after this URL there should be a code
|
|||
const parts = emailCall.html.split("http://localhost:10000/builder/invite?code=") |
|||
const code = parts[1].split("\"")[0].split("&")[0] |
|||
return { code, res } |
|||
} |
|||
|
|||
it("should be able to generate an invitation", async () => { |
|||
const { code, res } = await sendUserInvite() |
|||
|
|||
expect(res.body).toEqual({ message: "Invitation has been sent." }) |
|||
expect(sendMailMock).toHaveBeenCalled() |
|||
expect(code).toBeDefined() |
|||
expect(events.user.invited).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to create new user from invite", async () => { |
|||
const { code } = await sendUserInvite() |
|||
|
|||
const res = await request |
|||
.post(`/api/global/users/invite/accept`) |
|||
.send({ |
|||
password: "newpassword", |
|||
inviteCode: code, |
|||
}) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
expect(res.body._id).toBeDefined() |
|||
const user = await config.getUser("invite@test.com") |
|||
expect(user).toBeDefined() |
|||
expect(user._id).toEqual(res.body._id) |
|||
expect(events.user.inviteAccepted).toBeCalledTimes(1) |
|||
expect(events.user.inviteAccepted).toBeCalledWith(user) |
|||
}) |
|||
|
|||
const createUser = async (user) => { |
|||
const existing = await config.getUser(user.email) |
|||
if (existing) { |
|||
await deleteUser(existing._id) |
|||
} |
|||
return saveUser(user) |
|||
} |
|||
|
|||
const updateUser = async (user) => { |
|||
const existing = await config.getUser(user.email) |
|||
user._id = existing._id |
|||
return saveUser(user) |
|||
} |
|||
|
|||
const saveUser = async (user) => { |
|||
const res = await request |
|||
.post(`/api/global/users`) |
|||
.send(user) |
|||
.set(config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
return res.body |
|||
} |
|||
|
|||
|
|||
const bulkCreateUsers = async (users) => { |
|||
const res = await request |
|||
.post(`/api/global/users/bulkCreate`) |
|||
.send(users) |
|||
.set(config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
return res.body |
|||
} |
|||
|
|||
const bulkDeleteUsers = async (users) => { |
|||
const res = await request |
|||
.post(`/api/global/users/bulkDelete`) |
|||
.send(users) |
|||
.set(config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
return res.body |
|||
} |
|||
|
|||
|
|||
|
|||
const deleteUser = async (email) => { |
|||
const user = await config.getUser(email) |
|||
if (user) { |
|||
await request |
|||
.delete(`/api/global/users/${user._id}`) |
|||
.set(config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
} |
|||
|
|||
describe("create", () => { |
|||
it("should be able to create a basic user", async () => { |
|||
jest.clearAllMocks() |
|||
const user = structures.users.user({ email: "basic@test.com" }) |
|||
await createUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to bulkCreate users with different permissions", async () => { |
|||
jest.clearAllMocks() |
|||
const builder = structures.users.builderUser({ email: "bulkbasic@test.com" }) |
|||
const admin = structures.users.adminUser({ email: "bulkadmin@test.com" }) |
|||
const user = structures.users.user({ email: "bulkuser@test.com" }) |
|||
|
|||
let toCreate = { users: [builder, admin, user], groups: [] } |
|||
await bulkCreateUsers(toCreate) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(3) |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
|
|||
it("should be able to create an admin user", async () => { |
|||
jest.clearAllMocks() |
|||
const user = structures.users.adminUser({ email: "admin@test.com" }) |
|||
await createUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to create a builder user", async () => { |
|||
jest.clearAllMocks() |
|||
const user = structures.users.builderUser({ email: "builder@test.com" }) |
|||
await createUser(user) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to assign app roles", async () => { |
|||
jest.clearAllMocks() |
|||
const user = structures.users.user({ email: "assign-roles@test.com" }) |
|||
user.roles = { |
|||
"app_123": "role1", |
|||
"app_456": "role2", |
|||
} |
|||
|
|||
await createUser(user) |
|||
const savedUser = await config.getUser(user.email) |
|||
|
|||
expect(events.user.created).toBeCalledTimes(1) |
|||
expect(events.user.updated).not.toBeCalled() |
|||
expect(events.role.assigned).toBeCalledTimes(2) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
}) |
|||
|
|||
describe("update", () => { |
|||
it("should be able to update a basic user", async () => { |
|||
let user = structures.users.user({ email: "basic-update@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await updateUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
expect(events.user.passwordForceReset).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to force reset password", async () => { |
|||
let user = structures.users.user({ email: "basic-password-update@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.forceResetPassword = true |
|||
user.password = "tempPassword" |
|||
await updateUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
expect(events.user.passwordForceReset).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to update a basic user to an admin user", async () => { |
|||
let user = structures.users.user({ email: "basic-update-admin@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await updateUser(structures.users.adminUser(user)) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).not.toBeCalled() |
|||
expect(events.user.permissionAdminAssigned).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to update a basic user to a builder user", async () => { |
|||
let user = structures.users.user({ email: "basic-update-builder@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await updateUser(structures.users.builderUser(user)) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderAssigned).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminAssigned).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to update an admin user to a basic user", async () => { |
|||
let user = structures.users.adminUser({ email: "admin-update-basic@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.admin.global = false |
|||
await updateUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to update an builder user to a basic user", async () => { |
|||
let user = structures.users.builderUser({ email: "builder-update-basic@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.builder.global = false |
|||
await updateUser(user) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to assign app roles", async () => { |
|||
const user = structures.users.user({ email: "assign-roles-update@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.roles = { |
|||
"app_123": "role1", |
|||
"app_456": "role2", |
|||
} |
|||
await updateUser(user) |
|||
const savedUser = await config.getUser(user.email) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.assigned).toBeCalledTimes(2) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
|
|||
it("should be able to unassign app roles", async () => { |
|||
const user = structures.users.user({ email: "unassign-roles@test.com" }) |
|||
user.roles = { |
|||
"app_123": "role1", |
|||
"app_456": "role2", |
|||
} |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.roles = {} |
|||
await updateUser(user) |
|||
const savedUser = await config.getUser(user.email) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledTimes(2) |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role1") |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2") |
|||
}) |
|||
|
|||
it("should be able to update existing app roles", async () => { |
|||
const user = structures.users.user({ email: "update-roles@test.com" }) |
|||
user.roles = { |
|||
"app_123": "role1", |
|||
"app_456": "role2", |
|||
} |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
user.roles = { |
|||
"app_123": "role1", |
|||
"app_456": "role2-edit", |
|||
} |
|||
await updateUser(user) |
|||
const savedUser = await config.getUser(user.email) |
|||
|
|||
expect(events.user.created).not.toBeCalled() |
|||
expect(events.user.updated).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledTimes(1) |
|||
expect(events.role.unassigned).toBeCalledWith(savedUser, "role2") |
|||
expect(events.role.assigned).toBeCalledTimes(1) |
|||
expect(events.role.assigned).toBeCalledWith(savedUser, "role2-edit") |
|||
}) |
|||
}) |
|||
|
|||
describe("destroy", () => { |
|||
it("should be able to destroy a basic user", async () => { |
|||
let user = structures.users.user({ email: "destroy@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await deleteUser(user.email) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).not.toBeCalled() |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to destroy an admin user", async () => { |
|||
let user = structures.users.adminUser({ email: "destroy-admin@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await deleteUser(user.email) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).not.toBeCalled() |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
}) |
|||
|
|||
it("should be able to destroy a builder user", async () => { |
|||
let user = structures.users.builderUser({ email: "destroy-admin@test.com" }) |
|||
await createUser(user) |
|||
jest.clearAllMocks() |
|||
|
|||
await deleteUser(user.email) |
|||
|
|||
expect(events.user.deleted).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionAdminRemoved).not.toBeCalled() |
|||
}) |
|||
|
|||
it("should be able to bulk delete users with different permissions", async () => { |
|||
jest.clearAllMocks() |
|||
const builder = structures.users.builderUser({ email: "basic@test.com" }) |
|||
const admin = structures.users.adminUser({ email: "admin@test.com" }) |
|||
const user = structures.users.user({ email: "user@test.com" }) |
|||
|
|||
let toCreate = { users: [builder, admin, user], groups: [] } |
|||
let createdUsers = await bulkCreateUsers(toCreate) |
|||
await bulkDeleteUsers({ userIds: [createdUsers[0]._id, createdUsers[1]._id, createdUsers[2]._id] }) |
|||
expect(events.user.deleted).toBeCalledTimes(3) |
|||
expect(events.user.permissionAdminRemoved).toBeCalledTimes(1) |
|||
expect(events.user.permissionBuilderRemoved).toBeCalledTimes(1) |
|||
|
|||
}) |
|||
|
|||
}) |
|||
}) |
|||
@ -0,0 +1,53 @@ |
|||
import { AccountMetadata } from "@budibase/types" |
|||
import { |
|||
db, |
|||
StaticDatabases, |
|||
HTTPError, |
|||
DocumentType, |
|||
SEPARATOR, |
|||
} from "@budibase/backend-core" |
|||
|
|||
export const formatAccountMetadataId = (accountId: string) => { |
|||
return `${DocumentType.ACCOUNT_METADATA}${SEPARATOR}${accountId}` |
|||
} |
|||
|
|||
export const saveMetadata = async ( |
|||
metadata: AccountMetadata |
|||
): Promise<AccountMetadata> => { |
|||
return db.doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { |
|||
const existing = await getMetadata(metadata._id!) |
|||
if (existing) { |
|||
metadata._rev = existing._rev |
|||
} |
|||
const res = await db.put(metadata) |
|||
metadata._rev = res.rev |
|||
return metadata |
|||
}) |
|||
} |
|||
|
|||
export const getMetadata = async ( |
|||
accountId: string |
|||
): Promise<AccountMetadata | undefined> => { |
|||
return db.doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { |
|||
try { |
|||
return await db.get(accountId) |
|||
} catch (e: any) { |
|||
if (e.status === 404) { |
|||
// do nothing
|
|||
return |
|||
} else { |
|||
throw e |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export const destroyMetadata = async (accountId: string) => { |
|||
await db.doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => { |
|||
const metadata = await getMetadata(accountId) |
|||
if (!metadata) { |
|||
throw new HTTPError(`id=${accountId} does not exist`, 404) |
|||
} |
|||
await db.remove(accountId, metadata._rev) |
|||
}) |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export * from "./accounts" |
|||
@ -1 +1,2 @@ |
|||
export * as users from "./users" |
|||
export * as accounts from "./accounts" |
|||
|
|||
@ -1,231 +0,0 @@ |
|||
require("./mocks") |
|||
require("../db").init() |
|||
const env = require("../environment") |
|||
const controllers = require("./controllers") |
|||
const supertest = require("supertest") |
|||
const { jwt } = require("@budibase/backend-core/auth") |
|||
const { Cookies, Headers } = require("@budibase/backend-core/constants") |
|||
const { Configs } = require("../constants") |
|||
const { users } = require("@budibase/backend-core") |
|||
const { createASession } = require("@budibase/backend-core/sessions") |
|||
const { TENANT_ID, CSRF_TOKEN } = require("./structures") |
|||
const structures = require("./structures") |
|||
const { doInTenant } = require("@budibase/backend-core/tenancy") |
|||
const { groups } = require("@budibase/pro") |
|||
class TestConfiguration { |
|||
constructor(openServer = true) { |
|||
if (openServer) { |
|||
env.PORT = "0" // random port
|
|||
this.server = require("../index") |
|||
// we need the request for logging in, involves cookies, hard to fake
|
|||
this.request = supertest(this.server) |
|||
} |
|||
} |
|||
|
|||
getRequest() { |
|||
return this.request |
|||
} |
|||
|
|||
// UTILS
|
|||
|
|||
async _req(config, params, controlFunc) { |
|||
const request = {} |
|||
// fake cookies, we don't need them
|
|||
request.cookies = { set: () => {}, get: () => {} } |
|||
request.config = { jwtSecret: env.JWT_SECRET } |
|||
request.appId = this.appId |
|||
request.user = { appId: this.appId, tenantId: TENANT_ID } |
|||
request.query = {} |
|||
request.request = { |
|||
body: config, |
|||
} |
|||
request.throw = (status, err) => { |
|||
throw { status, message: err } |
|||
} |
|||
if (params) { |
|||
request.params = params |
|||
} |
|||
await doInTenant(TENANT_ID, () => { |
|||
return controlFunc(request) |
|||
}) |
|||
return request.body |
|||
} |
|||
|
|||
// SETUP / TEARDOWN
|
|||
|
|||
async beforeAll() { |
|||
await this.login() |
|||
} |
|||
|
|||
async afterAll() { |
|||
if (this.server) { |
|||
await this.server.close() |
|||
} |
|||
} |
|||
|
|||
// USER / AUTH
|
|||
|
|||
async login() { |
|||
// create a test user
|
|||
await this._req( |
|||
{ |
|||
email: "test@test.com", |
|||
password: "test", |
|||
_id: "us_uuid1", |
|||
builder: { |
|||
global: true, |
|||
}, |
|||
admin: { |
|||
global: true, |
|||
}, |
|||
}, |
|||
null, |
|||
controllers.users.save |
|||
) |
|||
await createASession("us_uuid1", { |
|||
sessionId: "sessionid", |
|||
tenantId: TENANT_ID, |
|||
csrfToken: CSRF_TOKEN, |
|||
}) |
|||
} |
|||
|
|||
cookieHeader(cookies) { |
|||
return { |
|||
Cookie: [cookies], |
|||
} |
|||
} |
|||
|
|||
defaultHeaders() { |
|||
const user = { |
|||
_id: "us_uuid1", |
|||
userId: "us_uuid1", |
|||
sessionId: "sessionid", |
|||
tenantId: TENANT_ID, |
|||
} |
|||
const authToken = jwt.sign(user, env.JWT_SECRET) |
|||
return { |
|||
Accept: "application/json", |
|||
...this.cookieHeader([`${Cookies.Auth}=${authToken}`]), |
|||
[Headers.CSRF_TOKEN]: CSRF_TOKEN, |
|||
} |
|||
} |
|||
|
|||
async getUser(email) { |
|||
return doInTenant(TENANT_ID, () => { |
|||
return users.getGlobalUserByEmail(email) |
|||
}) |
|||
} |
|||
|
|||
async getGroup(id) { |
|||
return doInTenant(TENANT_ID, () => { |
|||
return groups.get(id) |
|||
}) |
|||
} |
|||
|
|||
async saveGroup(group) { |
|||
const res = await this.getRequest() |
|||
.post(`/api/global/groups`) |
|||
.send(group) |
|||
.set(this.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
return res.body |
|||
} |
|||
|
|||
async createUser(email, password) { |
|||
const user = await this.getUser(structures.users.email) |
|||
if (user) { |
|||
return user |
|||
} |
|||
await this._req( |
|||
structures.users.user({ email, password }), |
|||
null, |
|||
controllers.users.save |
|||
) |
|||
} |
|||
|
|||
async saveAdminUser() { |
|||
await this._req( |
|||
structures.users.user({ tenantId: TENANT_ID }), |
|||
null, |
|||
controllers.users.adminUser |
|||
) |
|||
} |
|||
|
|||
// CONFIGS
|
|||
|
|||
async deleteConfig(type) { |
|||
try { |
|||
const cfg = await this._req( |
|||
null, |
|||
{ |
|||
type, |
|||
}, |
|||
controllers.config.find |
|||
) |
|||
if (cfg) { |
|||
await this._req( |
|||
null, |
|||
{ |
|||
id: cfg._id, |
|||
rev: cfg._rev, |
|||
}, |
|||
controllers.config.destroy |
|||
) |
|||
} |
|||
} catch (err) { |
|||
// don't need to handle error
|
|||
} |
|||
} |
|||
|
|||
// CONFIGS - SETTINGS
|
|||
|
|||
async saveSettingsConfig() { |
|||
await this.deleteConfig(Configs.SETTINGS) |
|||
await this._req( |
|||
structures.configs.settings(), |
|||
null, |
|||
controllers.config.save |
|||
) |
|||
} |
|||
|
|||
// CONFIGS - GOOGLE
|
|||
|
|||
async saveGoogleConfig() { |
|||
await this.deleteConfig(Configs.GOOGLE) |
|||
await this._req(structures.configs.google(), null, controllers.config.save) |
|||
} |
|||
|
|||
// CONFIGS - OIDC
|
|||
|
|||
getOIDConfigCookie(configId) { |
|||
const token = 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() |
|||
|
|||
await this._req(config, null, controllers.config.save) |
|||
return config |
|||
} |
|||
|
|||
// CONFIGS - SMTP
|
|||
|
|||
async saveSmtpConfig() { |
|||
await this.deleteConfig(Configs.SMTP) |
|||
await this._req(structures.configs.smtp(), null, controllers.config.save) |
|||
} |
|||
|
|||
async saveEtherealSmtpConfig() { |
|||
await this.deleteConfig(Configs.SMTP) |
|||
await this._req( |
|||
structures.configs.smtpEthereal(), |
|||
null, |
|||
controllers.config.save |
|||
) |
|||
} |
|||
} |
|||
|
|||
module.exports = TestConfiguration |
|||
@ -0,0 +1,273 @@ |
|||
import "./mocks" |
|||
import dbConfig from "../db" |
|||
dbConfig.init() |
|||
import env from "../environment" |
|||
import controllers from "./controllers" |
|||
const supertest = require("supertest") |
|||
import { Configs } from "../constants" |
|||
import { |
|||
users, |
|||
tenancy, |
|||
Cookies, |
|||
Headers, |
|||
sessions, |
|||
auth, |
|||
} from "@budibase/backend-core" |
|||
import { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures" |
|||
import structures from "./structures" |
|||
import { CreateUserResponse, User, AuthToken } from "@budibase/types" |
|||
|
|||
enum Mode { |
|||
ACCOUNT = "account", |
|||
SELF = "self", |
|||
} |
|||
|
|||
class TestConfiguration { |
|||
server: any |
|||
request: any |
|||
defaultUser?: User |
|||
tenant1User?: User |
|||
|
|||
constructor( |
|||
opts: { openServer: boolean; mode: Mode } = { |
|||
openServer: true, |
|||
mode: Mode.ACCOUNT, |
|||
} |
|||
) { |
|||
if (opts.mode === Mode.ACCOUNT) { |
|||
this.modeAccount() |
|||
} else if (opts.mode === Mode.SELF) { |
|||
this.modeSelf() |
|||
} |
|||
|
|||
if (opts.openServer) { |
|||
env.PORT = "0" // random port
|
|||
this.server = require("../index") |
|||
// we need the request for logging in, involves cookies, hard to fake
|
|||
this.request = supertest(this.server) |
|||
} |
|||
} |
|||
|
|||
getRequest() { |
|||
return this.request |
|||
} |
|||
|
|||
// MODES
|
|||
|
|||
modeAccount = () => { |
|||
env.SELF_HOSTED = false |
|||
// @ts-ignore
|
|||
env.MULTI_TENANCY = true |
|||
// @ts-ignore
|
|||
env.DISABLE_ACCOUNT_PORTAL = false |
|||
} |
|||
|
|||
modeSelf = () => { |
|||
env.SELF_HOSTED = true |
|||
// @ts-ignore
|
|||
env.MULTI_TENANCY = false |
|||
// @ts-ignore
|
|||
env.DISABLE_ACCOUNT_PORTAL = true |
|||
} |
|||
|
|||
// UTILS
|
|||
|
|||
async _req(config: any, params: any, controlFunc: any) { |
|||
const request: any = {} |
|||
// fake cookies, we don't need them
|
|||
request.cookies = { set: () => {}, get: () => {} } |
|||
request.config = { jwtSecret: env.JWT_SECRET } |
|||
request.user = { tenantId: this.getTenantId() } |
|||
request.query = {} |
|||
request.request = { |
|||
body: config, |
|||
} |
|||
request.throw = (status: any, err: any) => { |
|||
throw { status, message: err } |
|||
} |
|||
if (params) { |
|||
request.params = params |
|||
} |
|||
await tenancy.doInTenant(this.getTenantId(), () => { |
|||
return controlFunc(request) |
|||
}) |
|||
return request.body |
|||
} |
|||
|
|||
// SETUP / TEARDOWN
|
|||
|
|||
async beforeAll() { |
|||
await this.createDefaultUser() |
|||
await this.createSession(this.defaultUser!) |
|||
|
|||
await tenancy.doInTenant(TENANT_1, async () => { |
|||
await this.createTenant1User() |
|||
await this.createSession(this.tenant1User!) |
|||
}) |
|||
} |
|||
|
|||
async afterAll() { |
|||
if (this.server) { |
|||
await this.server.close() |
|||
} |
|||
} |
|||
|
|||
// TENANCY
|
|||
|
|||
getTenantId() { |
|||
try { |
|||
return tenancy.getTenantId() |
|||
} catch (e: any) { |
|||
return TENANT_ID |
|||
} |
|||
} |
|||
|
|||
// USER / AUTH
|
|||
|
|||
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 createSession(user: User) { |
|||
await sessions.createASession(user._id!, { |
|||
sessionId: "sessionid", |
|||
tenantId: user.tenantId, |
|||
csrfToken: CSRF_TOKEN, |
|||
}) |
|||
} |
|||
|
|||
cookieHeader(cookies: any) { |
|||
return { |
|||
Cookie: [cookies], |
|||
} |
|||
} |
|||
|
|||
authHeaders(user: User) { |
|||
const authToken: AuthToken = { |
|||
userId: user._id!, |
|||
sessionId: "sessionid", |
|||
tenantId: user.tenantId, |
|||
} |
|||
const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET) |
|||
return { |
|||
Accept: "application/json", |
|||
...this.cookieHeader([`${Cookies.Auth}=${authCookie}`]), |
|||
[Headers.CSRF_TOKEN]: CSRF_TOKEN, |
|||
} |
|||
} |
|||
|
|||
defaultHeaders() { |
|||
const tenantId = this.getTenantId() |
|||
if (tenantId === TENANT_ID) { |
|||
return this.authHeaders(this.defaultUser!) |
|||
} else if (tenantId === TENANT_1) { |
|||
return this.authHeaders(this.tenant1User!) |
|||
} else { |
|||
throw new Error("could not determine auth headers to use") |
|||
} |
|||
} |
|||
|
|||
async getUser(email: string): Promise<User> { |
|||
return tenancy.doInTenant(this.getTenantId(), () => { |
|||
return users.getGlobalUserByEmail(email) |
|||
}) |
|||
} |
|||
|
|||
async createUser(user?: User) { |
|||
if (!user) { |
|||
user = structures.users.user() |
|||
} |
|||
const response = await this._req(user, null, controllers.users.save) |
|||
const body = response as CreateUserResponse |
|||
return this.getUser(body.email) |
|||
} |
|||
|
|||
// CONFIGS
|
|||
|
|||
async deleteConfig(type: any) { |
|||
try { |
|||
const cfg = await this._req( |
|||
null, |
|||
{ |
|||
type, |
|||
}, |
|||
controllers.config.find |
|||
) |
|||
if (cfg) { |
|||
await this._req( |
|||
null, |
|||
{ |
|||
id: cfg._id, |
|||
rev: cfg._rev, |
|||
}, |
|||
controllers.config.destroy |
|||
) |
|||
} |
|||
} catch (err) { |
|||
// don't need to handle error
|
|||
} |
|||
} |
|||
|
|||
// CONFIGS - SETTINGS
|
|||
|
|||
async saveSettingsConfig() { |
|||
await this.deleteConfig(Configs.SETTINGS) |
|||
await this._req( |
|||
structures.configs.settings(), |
|||
null, |
|||
controllers.config.save |
|||
) |
|||
} |
|||
|
|||
// CONFIGS - GOOGLE
|
|||
|
|||
async saveGoogleConfig() { |
|||
await this.deleteConfig(Configs.GOOGLE) |
|||
await this._req(structures.configs.google(), null, controllers.config.save) |
|||
} |
|||
|
|||
// 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() |
|||
|
|||
await this._req(config, null, controllers.config.save) |
|||
return config |
|||
} |
|||
|
|||
// CONFIGS - SMTP
|
|||
|
|||
async saveSmtpConfig() { |
|||
await this.deleteConfig(Configs.SMTP) |
|||
await this._req(structures.configs.smtp(), null, controllers.config.save) |
|||
} |
|||
|
|||
async saveEtherealSmtpConfig() { |
|||
await this.deleteConfig(Configs.SMTP) |
|||
await this._req( |
|||
structures.configs.smtpEthereal(), |
|||
null, |
|||
controllers.config.save |
|||
) |
|||
} |
|||
} |
|||
|
|||
export = TestConfiguration |
|||
@ -0,0 +1,28 @@ |
|||
import { Account, AccountMetadata } from "@budibase/types" |
|||
import TestConfiguration from "../TestConfiguration" |
|||
|
|||
export class AccountAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
saveMetadata = async (account: Account) => { |
|||
const res = await this.request |
|||
.put(`/api/system/accounts/${account.accountId}/metadata`) |
|||
.send(account) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
return res.body as AccountMetadata |
|||
} |
|||
|
|||
destroyMetadata = (accountId: string) => { |
|||
return this.request |
|||
.del(`/api/system/accounts/${accountId}/metadata`) |
|||
.set(this.config.defaultHeaders()) |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
import TestConfiguration from "../TestConfiguration" |
|||
|
|||
export class AuthAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
updatePassword = (code: string) => { |
|||
return this.request |
|||
.post(`/api/global/auth/${this.config.getTenantId()}/reset/update`) |
|||
.send({ |
|||
password: "newpassword", |
|||
resetCode: code, |
|||
}) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
|
|||
logout = () => { |
|||
return this.request |
|||
.post("/api/global/auth/logout") |
|||
.set(this.config.defaultHeaders()) |
|||
.expect(200) |
|||
} |
|||
|
|||
requestPasswordReset = async (sendMailMock: any) => { |
|||
await this.config.saveSmtpConfig() |
|||
await this.config.saveSettingsConfig() |
|||
await this.config.createUser() |
|||
const res = await this.request |
|||
.post(`/api/global/auth/${this.config.getTenantId()}/reset`) |
|||
.send({ |
|||
email: "test@test.com", |
|||
}) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
const emailCall = sendMailMock.mock.calls[0][0] |
|||
const parts = emailCall.html.split( |
|||
`http://localhost:10000/builder/auth/reset?code=` |
|||
) |
|||
const code = parts[1].split('"')[0].split("&")[0] |
|||
return { code, res } |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
import TestConfiguration from "../TestConfiguration" |
|||
|
|||
export class ConfigAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
getConfigChecklist = () => { |
|||
return this.request |
|||
.get(`/api/global/configs/checklist`) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
|
|||
saveConfig = (data: any) => { |
|||
return this.request |
|||
.post(`/api/global/configs`) |
|||
.send(data) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
|
|||
OIDCCallback = (configId: string) => { |
|||
return this.request |
|||
.get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`) |
|||
.set(this.config.getOIDConfigCookie(configId)) |
|||
} |
|||
|
|||
getOIDCConfig = (configId: string) => { |
|||
return this.request.get( |
|||
`/api/global/auth/${this.config.getTenantId()}/oidc/configs/${configId}` |
|||
) |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import TestConfiguration from "../TestConfiguration" |
|||
|
|||
export class EmailAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
sendEmail = (purpose: string) => { |
|||
return this.request |
|||
.post(`/api/global/email/send`) |
|||
.send({ |
|||
email: "test@test.com", |
|||
purpose, |
|||
tenantId: this.config.getTenantId(), |
|||
}) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
import TestConfiguration from "../TestConfiguration" |
|||
import { AccountAPI } from "./accounts" |
|||
import { AuthAPI } from "./auth" |
|||
import { ConfigAPI } from "./configs" |
|||
import { EmailAPI } from "./email" |
|||
import { SelfAPI } from "./self" |
|||
import { UserAPI } from "./users" |
|||
|
|||
export default class API { |
|||
accounts: AccountAPI |
|||
auth: AuthAPI |
|||
configs: ConfigAPI |
|||
emails: EmailAPI |
|||
self: SelfAPI |
|||
users: UserAPI |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.accounts = new AccountAPI(config) |
|||
this.auth = new AuthAPI(config) |
|||
this.configs = new ConfigAPI(config) |
|||
this.emails = new EmailAPI(config) |
|||
this.self = new SelfAPI(config) |
|||
this.users = new UserAPI(config) |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
import TestConfiguration from "../TestConfiguration" |
|||
import { User } from "@budibase/types" |
|||
|
|||
export class SelfAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
updateSelf = (user: User) => { |
|||
return this.request |
|||
.post(`/api/global/self`) |
|||
.send(user) |
|||
.set(this.config.authHeaders(user)) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
import { |
|||
BulkCreateUsersRequest, |
|||
BulkCreateUsersResponse, |
|||
BulkDeleteUsersRequest, |
|||
CreateUserResponse, |
|||
User, |
|||
UserDetails, |
|||
} from "@budibase/types" |
|||
import TestConfiguration from "../TestConfiguration" |
|||
|
|||
export class UserAPI { |
|||
config: TestConfiguration |
|||
request: any |
|||
|
|||
constructor(config: TestConfiguration) { |
|||
this.config = config |
|||
this.request = config.request |
|||
} |
|||
|
|||
// INVITE
|
|||
|
|||
sendUserInvite = async (sendMailMock: any) => { |
|||
await this.config.saveSmtpConfig() |
|||
await this.config.saveSettingsConfig() |
|||
const res = await this.request |
|||
.post(`/api/global/users/invite`) |
|||
.send({ |
|||
email: "invite@test.com", |
|||
}) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
|
|||
const emailCall = sendMailMock.mock.calls[0][0] |
|||
// after this URL there should be a code
|
|||
const parts = emailCall.html.split( |
|||
"http://localhost:10000/builder/invite?code=" |
|||
) |
|||
const code = parts[1].split('"')[0].split("&")[0] |
|||
return { code, res } |
|||
} |
|||
|
|||
acceptInvite = (code: string) => { |
|||
return this.request |
|||
.post(`/api/global/users/invite/accept`) |
|||
.send({ |
|||
password: "newpassword", |
|||
inviteCode: code, |
|||
}) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
} |
|||
|
|||
// BULK
|
|||
|
|||
bulkCreateUsers = async (users: User[], groups: any[] = []) => { |
|||
const body: BulkCreateUsersRequest = { users, groups } |
|||
const res = await this.request |
|||
.post(`/api/global/users/bulkCreate`) |
|||
.send(body) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(200) |
|||
|
|||
return res.body as BulkCreateUsersResponse |
|||
} |
|||
|
|||
bulkDeleteUsers = (body: BulkDeleteUsersRequest, status?: number) => { |
|||
return this.request |
|||
.post(`/api/global/users/bulkDelete`) |
|||
.send(body) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(status ? status : 200) |
|||
} |
|||
|
|||
// USER
|
|||
|
|||
saveUser = (user: User, status?: number) => { |
|||
return this.request |
|||
.post(`/api/global/users`) |
|||
.send(user) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(status ? status : 200) |
|||
} |
|||
|
|||
deleteUser = (userId: string, status?: number) => { |
|||
return this.request |
|||
.delete(`/api/global/users/${userId}`) |
|||
.set(this.config.defaultHeaders()) |
|||
.expect("Content-Type", /json/) |
|||
.expect(status ? status : 200) |
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
const TestConfiguration = require("./TestConfiguration") |
|||
const structures = require("./structures") |
|||
const mocks = require("./mocks") |
|||
const config = new TestConfiguration() |
|||
const request = config.getRequest() |
|||
|
|||
module.exports = { |
|||
structures, |
|||
mocks, |
|||
config, |
|||
request, |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
import TestConfiguration from "./TestConfiguration" |
|||
import structures from "./structures" |
|||
import mocks from "./mocks" |
|||
import API from "./api" |
|||
|
|||
const pkg = { |
|||
structures, |
|||
TENANT_1: structures.TENANT_1, |
|||
mocks, |
|||
TestConfiguration, |
|||
API, |
|||
} |
|||
|
|||
export = pkg |
|||
@ -1,5 +0,0 @@ |
|||
const email = require("./email") |
|||
|
|||
module.exports = { |
|||
email, |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
const email = require("./email") |
|||
import { mocks as coreMocks } from "@budibase/backend-core/tests" |
|||
|
|||
export = { |
|||
email, |
|||
...coreMocks, |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
import { Account, AuthType, Hosting, CloudAccount } from "@budibase/types" |
|||
import { v4 as uuid } from "uuid" |
|||
import { utils } from "@budibase/backend-core" |
|||
|
|||
export const account = (): Account => { |
|||
return { |
|||
email: `${uuid()}@test.com`, |
|||
tenantId: utils.newid(), |
|||
hosting: Hosting.SELF, |
|||
authType: AuthType.SSO, |
|||
accountId: uuid(), |
|||
createdAt: Date.now(), |
|||
verified: true, |
|||
verificationSent: true, |
|||
tier: "FREE", |
|||
} |
|||
} |
|||
|
|||
export const cloudAccount = (): CloudAccount => { |
|||
return { |
|||
...account(), |
|||
budibaseUserId: uuid(), |
|||
} |
|||
} |
|||
@ -1,14 +0,0 @@ |
|||
const configs = require("./configs") |
|||
const users = require("./users") |
|||
const groups = require("./groups") |
|||
|
|||
const TENANT_ID = "default" |
|||
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" |
|||
|
|||
module.exports = { |
|||
configs, |
|||
users, |
|||
TENANT_ID, |
|||
CSRF_TOKEN, |
|||
groups, |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
import configs from "./configs" |
|||
import * as users from "./users" |
|||
import * as groups from "./groups" |
|||
import * as accounts from "./accounts" |
|||
|
|||
const TENANT_ID = "default" |
|||
const TENANT_1 = "tenant1" |
|||
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" |
|||
|
|||
const pkg = { |
|||
configs, |
|||
users, |
|||
accounts, |
|||
TENANT_ID, |
|||
TENANT_1, |
|||
CSRF_TOKEN, |
|||
groups, |
|||
} |
|||
|
|||
export = pkg |
|||
Loading…
Reference in new issue