540 lines
19 KiB
TypeScript
540 lines
19 KiB
TypeScript
import { InviteUsersResponse } from "@budibase/types"
|
|
|
|
jest.mock("nodemailer")
|
|
import {
|
|
TestConfiguration,
|
|
mocks,
|
|
structures,
|
|
TENANT_1,
|
|
} from "../../../../tests"
|
|
const sendMailMock = mocks.email.mock()
|
|
import { events, tenancy } from "@budibase/backend-core"
|
|
|
|
describe("/api/global/users", () => {
|
|
const config = new TestConfiguration()
|
|
|
|
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 email = structures.users.newEmail()
|
|
const { code, res } = await config.api.users.sendUserInvite(
|
|
sendMailMock,
|
|
email
|
|
)
|
|
|
|
expect(res.body).toEqual({ message: "Invitation has been sent." })
|
|
expect(sendMailMock).toHaveBeenCalled()
|
|
expect(code).toBeDefined()
|
|
expect(events.user.invited).toBeCalledTimes(1)
|
|
})
|
|
|
|
it("should not be able to generate an invitation for existing user", async () => {
|
|
const { code, res } = await config.api.users.sendUserInvite(
|
|
sendMailMock,
|
|
config.defaultUser!.email,
|
|
400
|
|
)
|
|
|
|
expect(res.body.message).toBe("Unavailable")
|
|
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
expect(code).toBeUndefined()
|
|
expect(events.user.invited).toBeCalledTimes(0)
|
|
})
|
|
|
|
it("should be able to create new user from invite", async () => {
|
|
const email = structures.users.newEmail()
|
|
const { code } = await config.api.users.sendUserInvite(
|
|
sendMailMock,
|
|
email
|
|
)
|
|
|
|
const res = await config.api.users.acceptInvite(code)
|
|
|
|
expect(res.body._id).toBeDefined()
|
|
const user = await config.getUser(email)
|
|
expect(user).toBeDefined()
|
|
expect(user._id).toEqual(res.body._id)
|
|
expect(events.user.inviteAccepted).toBeCalledTimes(1)
|
|
expect(events.user.inviteAccepted).toBeCalledWith(user)
|
|
})
|
|
})
|
|
|
|
describe("inviteMultiple", () => {
|
|
it("should be able to generate an invitation", async () => {
|
|
const newUserInvite = () => ({
|
|
email: structures.users.newEmail(),
|
|
userInfo: {},
|
|
})
|
|
const request = [newUserInvite(), newUserInvite()]
|
|
|
|
const res = await config.api.users.sendMultiUserInvite(request)
|
|
|
|
const body = res.body as InviteUsersResponse
|
|
expect(body.successful.length).toBe(2)
|
|
expect(body.unsuccessful.length).toBe(0)
|
|
expect(sendMailMock).toHaveBeenCalledTimes(2)
|
|
expect(events.user.invited).toBeCalledTimes(2)
|
|
})
|
|
|
|
it("should not be able to generate an invitation for existing user", async () => {
|
|
const request = [{ email: config.defaultUser!.email, userInfo: {} }]
|
|
|
|
const res = await config.api.users.sendMultiUserInvite(request)
|
|
|
|
const body = res.body as InviteUsersResponse
|
|
expect(body.successful.length).toBe(0)
|
|
expect(body.unsuccessful.length).toBe(1)
|
|
expect(body.unsuccessful[0].reason).toBe("Unavailable")
|
|
expect(sendMailMock).toHaveBeenCalledTimes(0)
|
|
expect(events.user.invited).toBeCalledTimes(0)
|
|
})
|
|
})
|
|
|
|
describe("bulk (create)", () => {
|
|
it("should ignore users existing in the same tenant", async () => {
|
|
const user = await config.createUser()
|
|
jest.clearAllMocks()
|
|
|
|
const response = await config.api.users.bulkCreateUsers([user])
|
|
|
|
expect(response.created?.successful.length).toBe(0)
|
|
expect(response.created?.unsuccessful.length).toBe(1)
|
|
expect(response.created?.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 config.api.users.bulkCreateUsers([user])
|
|
|
|
expect(response.created?.successful.length).toBe(0)
|
|
expect(response.created?.unsuccessful.length).toBe(1)
|
|
expect(response.created?.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 config.api.accounts.saveMetadata(account)
|
|
const user = structures.users.user({ email: resp.email })
|
|
jest.clearAllMocks()
|
|
|
|
const response = await config.api.users.bulkCreateUsers([user])
|
|
|
|
expect(response.created?.successful.length).toBe(0)
|
|
expect(response.created?.unsuccessful.length).toBe(1)
|
|
expect(response.created?.unsuccessful[0].email).toBe(user.email)
|
|
expect(events.user.created).toBeCalledTimes(0)
|
|
})
|
|
|
|
it("should be able to bulk create users", async () => {
|
|
const builder = structures.users.builderUser()
|
|
const admin = structures.users.adminUser()
|
|
const user = structures.users.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)
|
|
expect(response.created?.successful[1].email).toBe(admin.email)
|
|
expect(response.created?.successful[2].email).toBe(user.email)
|
|
expect(response.created?.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 config.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 config.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 config.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 config.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 config.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 config.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 config.api.users.saveUser(user, 400)
|
|
|
|
expect(response.body.message).toBe(`Unavailable`)
|
|
expect(events.user.created).toBeCalledTimes(0)
|
|
})
|
|
|
|
it("should not be able to create a user with the same email and different casing", async () => {
|
|
const user = structures.users.user()
|
|
await config.api.users.saveUser(user)
|
|
|
|
user.email = user.email.toUpperCase()
|
|
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 config.api.users.saveUser(user)
|
|
|
|
user.email = user.email.toUpperCase()
|
|
await config.api.users.bulkCreateUsers([user])
|
|
|
|
expect(events.user.created).toBeCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("update", () => {
|
|
it("should be able to update a basic user", async () => {
|
|
const user = await config.createUser()
|
|
jest.clearAllMocks()
|
|
|
|
await config.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 config.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 config.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 config.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 config.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 config.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 config.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 config.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 config.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 config.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("bulk (delete)", () => {
|
|
it("should not be able to bulk delete current user", async () => {
|
|
const user = await config.defaultUser!
|
|
|
|
const response = await config.api.users.bulkDeleteUsers([user._id!], 400)
|
|
|
|
expect(response.message).toBe("Unable to delete self.")
|
|
expect(events.user.deleted).not.toBeCalled()
|
|
})
|
|
|
|
it("should not be able to bulk delete account owner", async () => {
|
|
const user = await config.createUser()
|
|
const account = structures.accounts.cloudAccount()
|
|
account.budibaseUserId = user._id!
|
|
mocks.accounts.getAccountByTenantId.mockReturnValue(account)
|
|
|
|
const response = await config.api.users.bulkDeleteUsers([user._id!])
|
|
|
|
expect(response.deleted?.successful.length).toBe(0)
|
|
expect(response.deleted?.unsuccessful.length).toBe(1)
|
|
expect(response.deleted?.unsuccessful[0].reason).toBe(
|
|
"Account holder cannot be deleted"
|
|
)
|
|
expect(response.deleted?.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 config.api.users.bulkCreateUsers([
|
|
builder,
|
|
admin,
|
|
user,
|
|
])
|
|
|
|
const toDelete = createdUsers.created?.successful.map(
|
|
u => u._id!
|
|
) as string[]
|
|
const response = await config.api.users.bulkDeleteUsers(toDelete)
|
|
|
|
expect(response.deleted?.successful.length).toBe(3)
|
|
expect(response.deleted?.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 config.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 config.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 config.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 config.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 config.api.users.deleteUser(user._id!, 400)
|
|
|
|
expect(response.body.message).toBe("Unable to delete self.")
|
|
})
|
|
})
|
|
})
|