budibase/packages/server/src/api/routes/tests/rowAction.spec.ts

834 lines
23 KiB
TypeScript
Raw Normal View History

2024-07-10 13:24:25 +02:00
import _ from "lodash"
2024-07-11 10:04:25 +02:00
import tk from "timekeeper"
2024-07-18 17:11:11 +02:00
import {
CreateRowActionRequest,
DocumentType,
2024-08-26 13:42:20 +02:00
PermissionLevel,
2024-08-26 15:17:18 +02:00
Row,
2024-07-18 17:11:11 +02:00
RowActionResponse,
} from "@budibase/types"
2024-07-10 13:24:25 +02:00
import * as setup from "./utilities"
import { generator } from "@budibase/backend-core/tests"
2024-08-26 12:43:35 +02:00
import { Expectations } from "../../../tests/utilities/api/base"
2024-08-26 13:42:20 +02:00
import { roles } from "@budibase/backend-core"
2024-08-26 15:17:18 +02:00
import { automations } from "@budibase/pro"
2024-07-10 13:24:25 +02:00
const expectAutomationId = () =>
expect.stringMatching(`^${DocumentType.AUTOMATION}_.+`)
2024-07-10 13:24:25 +02:00
describe("/rowsActions", () => {
const config = setup.getConfig()
2024-07-11 10:13:28 +02:00
let tableId: string
2024-07-10 13:24:25 +02:00
beforeAll(async () => {
2024-07-11 10:04:25 +02:00
tk.freeze(new Date())
2024-07-10 13:24:25 +02:00
await config.init()
2024-07-11 10:13:28 +02:00
})
2024-07-10 13:24:25 +02:00
2024-07-11 10:13:28 +02:00
beforeEach(async () => {
const table = await config.api.table.save(setup.structures.basicTable())
tableId = table._id!
2024-07-10 13:24:25 +02:00
})
afterAll(setup.afterAll)
2024-07-17 11:15:55 +02:00
const createRowAction = config.api.rowAction.save
2024-07-11 10:46:29 +02:00
2024-07-10 15:41:55 +02:00
function createRowActionRequest(): CreateRowActionRequest {
return {
2024-07-17 11:52:29 +02:00
name: generator.string(),
2024-07-10 15:41:55 +02:00
}
}
2024-07-11 16:57:32 +02:00
function createRowActionRequests(count: number): CreateRowActionRequest[] {
return generator
2024-07-17 11:52:29 +02:00
.unique(() => generator.string(), count)
2024-07-11 16:57:32 +02:00
.map(name => ({ name }))
}
2024-08-26 12:43:35 +02:00
function unauthorisedTests(
apiDelegate: (
expectations: Expectations,
testConfig?: { publicUser?: boolean }
) => Promise<any>
) {
2024-07-10 13:24:25 +02:00
it("returns unauthorised (401) for unauthenticated requests", async () => {
2024-08-26 12:43:35 +02:00
await apiDelegate(
2024-07-10 13:24:25 +02:00
{
status: 401,
body: {
message: "Session not authenticated",
},
},
{ publicUser: true }
)
})
it("returns forbidden (403) for non-builder users", async () => {
const user = await config.createUser({
builder: {},
})
await config.withUser(user, async () => {
2024-07-11 10:46:29 +02:00
await createRowAction(generator.guid(), createRowActionRequest(), {
status: 403,
2024-08-26 13:42:20 +02:00
body: {
message: "Not Authorized",
},
})
})
})
it("returns forbidden (403) for non-builder users even if they have table write permissions", async () => {
const user = await config.createUser({
builder: {},
})
const tableId = generator.guid()
for (const role of Object.values(roles.BUILTIN_ROLE_IDS)) {
await config.api.permission.add({
roleId: role,
resourceId: tableId,
level: PermissionLevel.EXECUTE,
})
}
2024-08-26 14:38:18 +02:00
// replicate changes before checking permissions
await config.publish()
2024-08-26 13:42:20 +02:00
await config.withUser(user, async () => {
await createRowAction(tableId, createRowActionRequest(), {
status: 403,
body: {
message: "Not Authorized",
},
2024-07-11 10:46:29 +02:00
})
2024-07-10 13:24:25 +02:00
})
})
2024-07-10 13:56:41 +02:00
it("rejects (404) for a non-existing table", async () => {
2024-07-11 10:46:29 +02:00
await createRowAction(generator.guid(), createRowActionRequest(), {
status: 404,
})
2024-07-10 13:56:41 +02:00
})
2024-07-10 13:24:25 +02:00
}
describe("create", () => {
2024-08-26 12:43:35 +02:00
unauthorisedTests((expectations, testConfig) =>
createRowAction(
tableId,
createRowActionRequest(),
expectations,
testConfig
)
)
2024-07-10 13:24:25 +02:00
2024-07-11 10:13:28 +02:00
it("creates new row actions for tables without existing actions", async () => {
2024-07-10 15:41:55 +02:00
const rowAction = createRowActionRequest()
2024-07-11 16:57:32 +02:00
const res = await createRowAction(tableId, rowAction, {
status: 201,
})
2024-07-10 13:24:25 +02:00
2024-07-11 10:04:25 +02:00
expect(res).toEqual({
name: rowAction.name,
2024-07-11 17:16:14 +02:00
id: expect.stringMatching(/^row_action_\w+/),
2024-07-11 10:13:28 +02:00
tableId: tableId,
automationId: expectAutomationId(),
2024-07-11 10:13:28 +02:00
})
2024-07-11 16:57:32 +02:00
expect(await config.api.rowAction.find(tableId)).toEqual({
actions: {
2024-07-12 11:29:00 +02:00
[res.id]: {
name: rowAction.name,
2024-07-12 11:29:00 +02:00
id: res.id,
tableId: tableId,
automationId: expectAutomationId(),
2024-07-12 11:29:00 +02:00
},
2024-07-11 16:57:32 +02:00
},
2024-07-11 10:04:25 +02:00
})
2024-07-10 13:24:25 +02:00
})
2024-07-10 15:48:16 +02:00
2024-07-17 12:16:14 +02:00
it("trims row action names", async () => {
const name = " action name "
2024-08-29 16:11:05 +02:00
const res = await createRowAction(tableId, { name })
2024-07-17 12:16:14 +02:00
expect(res).toEqual(
expect.objectContaining({
2024-07-19 10:58:09 +02:00
name: "action name",
})
)
2024-07-17 12:16:14 +02:00
expect(await config.api.rowAction.find(tableId)).toEqual({
actions: {
[res.id]: expect.objectContaining({
name: "action name",
}),
},
})
})
2024-07-11 16:57:32 +02:00
it("can create multiple row actions for the same table", async () => {
const rowActions = createRowActionRequests(3)
const responses: RowActionResponse[] = []
for (const action of rowActions) {
responses.push(await createRowAction(tableId, action))
}
2024-07-11 10:19:11 +02:00
2024-07-11 16:57:32 +02:00
expect(await config.api.rowAction.find(tableId)).toEqual({
actions: {
2024-07-18 17:11:11 +02:00
[responses[0].id]: {
name: rowActions[0].name,
2024-07-18 17:11:11 +02:00
id: responses[0].id,
tableId,
2024-07-19 11:15:17 +02:00
automationId: expectAutomationId(),
2024-07-18 17:11:11 +02:00
},
[responses[1].id]: {
name: rowActions[1].name,
2024-07-18 17:11:11 +02:00
id: responses[1].id,
tableId,
2024-07-19 11:15:17 +02:00
automationId: expectAutomationId(),
2024-07-18 17:11:11 +02:00
},
[responses[2].id]: {
name: rowActions[2].name,
2024-07-18 17:11:11 +02:00
id: responses[2].id,
tableId,
2024-07-19 11:15:17 +02:00
automationId: expectAutomationId(),
2024-07-18 17:11:11 +02:00
},
2024-07-11 16:57:32 +02:00
},
2024-07-11 10:19:11 +02:00
})
})
2024-07-10 15:48:16 +02:00
it("rejects with bad request when creating with no name", async () => {
const rowAction: CreateRowActionRequest = {
2024-07-10 15:49:13 +02:00
name: "",
2024-07-10 15:48:16 +02:00
}
2024-07-11 10:46:29 +02:00
await createRowAction(tableId, rowAction, {
2024-07-10 15:48:16 +02:00
status: 400,
body: {
2024-07-10 15:49:13 +02:00
message: 'Invalid body - "name" is not allowed to be empty',
2024-07-10 15:48:16 +02:00
},
})
})
2024-07-12 12:17:05 +02:00
it("ignores not valid row action data", async () => {
const rowAction = createRowActionRequest()
const dirtyRowAction = {
name: rowAction.name,
2024-07-12 12:17:05 +02:00
id: generator.guid(),
2024-07-17 11:52:29 +02:00
valueToIgnore: generator.string(),
2024-07-12 12:17:05 +02:00
}
2024-08-29 16:11:05 +02:00
const res = await createRowAction(tableId, dirtyRowAction)
2024-07-12 12:17:05 +02:00
expect(res).toEqual({
name: rowAction.name,
2024-07-12 12:17:05 +02:00
id: expect.any(String),
tableId,
automationId: expectAutomationId(),
2024-07-12 12:17:05 +02:00
})
expect(await config.api.rowAction.find(tableId)).toEqual({
actions: {
[res.id]: {
name: rowAction.name,
2024-07-12 12:17:05 +02:00
id: res.id,
tableId: tableId,
2024-07-19 11:15:17 +02:00
automationId: expectAutomationId(),
2024-07-12 12:17:05 +02:00
},
},
})
})
2024-07-17 12:18:09 +02:00
it("can not create multiple row actions with the same name (for the same table)", async () => {
const action = await createRowAction(tableId, {
name: "Row action name ",
})
await createRowAction(
tableId,
{ name: action.name },
{
status: 409,
body: {
message: "A row action with the same name already exists.",
},
}
)
await createRowAction(
tableId,
{ name: "row action name" },
{
status: 409,
body: {
message: "A row action with the same name already exists.",
},
}
)
})
2024-07-17 12:26:36 +02:00
it("can reuse row action names between different tables", async () => {
const otherTable = await config.api.table.save(
setup.structures.basicTable()
)
const action = await createRowAction(tableId, createRowActionRequest())
await createRowAction(otherTable._id!, { name: action.name })
})
2024-07-19 10:58:09 +02:00
it("an automation is created when creating a new row action", async () => {
const action1 = await createRowAction(tableId, createRowActionRequest())
const action2 = await createRowAction(tableId, createRowActionRequest())
for (const automationId of [action1.automationId, action2.automationId]) {
2024-08-29 16:11:05 +02:00
expect(await config.api.automation.get(automationId)).toEqual(
expect.objectContaining({ _id: automationId })
)
2024-07-19 10:58:09 +02:00
}
})
2024-07-10 13:56:41 +02:00
})
2024-07-10 13:24:25 +02:00
2024-07-10 13:56:41 +02:00
describe("find", () => {
2024-08-26 12:43:35 +02:00
unauthorisedTests((expectations, testConfig) =>
config.api.rowAction.find(tableId, expectations, testConfig)
)
2024-07-10 13:56:41 +02:00
2024-07-11 10:59:11 +02:00
it("returns only the actions for the requested table", async () => {
2024-07-11 16:57:32 +02:00
const rowActions: RowActionResponse[] = []
for (const action of createRowActionRequests(3)) {
rowActions.push(await createRowAction(tableId, action))
2024-07-11 10:46:29 +02:00
}
const otherTable = await config.api.table.save(
setup.structures.basicTable()
)
2024-07-11 16:57:32 +02:00
await createRowAction(otherTable._id!, createRowActionRequest())
2024-07-11 10:59:11 +02:00
const response = await config.api.rowAction.find(tableId)
2024-07-12 11:29:00 +02:00
expect(response).toEqual({
actions: {
[rowActions[0].id]: expect.any(Object),
[rowActions[1].id]: expect.any(Object),
[rowActions[2].id]: expect.any(Object),
},
})
2024-07-11 10:59:11 +02:00
})
2024-07-11 11:06:36 +02:00
it("returns empty for tables without row actions", async () => {
const response = await config.api.rowAction.find(tableId)
2024-07-12 11:29:00 +02:00
expect(response).toEqual({
actions: {},
})
2024-07-11 10:46:29 +02:00
})
2024-07-10 13:24:25 +02:00
})
2024-07-11 17:08:57 +02:00
describe("update", () => {
2024-08-26 12:43:35 +02:00
unauthorisedTests((expectations, testConfig) =>
config.api.rowAction.update(
tableId,
generator.guid(),
createRowActionRequest(),
expectations,
testConfig
)
)
2024-07-11 17:08:57 +02:00
it("can update existing actions", async () => {
for (const rowAction of createRowActionRequests(3)) {
await createRowAction(tableId, rowAction)
}
const persisted = await config.api.rowAction.find(tableId)
const [actionId, actionData] = _.sample(
Object.entries(persisted.actions)
)!
2024-07-17 11:52:29 +02:00
const updatedName = generator.string()
2024-07-11 17:08:57 +02:00
const res = await config.api.rowAction.update(tableId, actionId, {
name: updatedName,
})
expect(res).toEqual({
2024-07-11 17:16:14 +02:00
id: actionId,
2024-07-11 17:08:57 +02:00
tableId,
name: updatedName,
automationId: actionData.automationId,
2024-07-11 17:08:57 +02:00
})
expect(await config.api.rowAction.find(tableId)).toEqual(
expect.objectContaining({
actions: expect.objectContaining({
[actionId]: {
name: updatedName,
id: actionData.id,
tableId: actionData.tableId,
automationId: actionData.automationId,
2024-07-11 17:08:57 +02:00
},
}),
})
)
})
2024-07-11 17:14:14 +02:00
2024-07-17 12:16:14 +02:00
it("trims row action names", async () => {
2024-08-29 16:11:05 +02:00
const rowAction = await createRowAction(tableId, createRowActionRequest())
2024-07-17 12:16:14 +02:00
const res = await config.api.rowAction.update(tableId, rowAction.id, {
name: " action name ",
})
expect(res).toEqual(expect.objectContaining({ name: "action name" }))
expect(await config.api.rowAction.find(tableId)).toEqual(
expect.objectContaining({
actions: expect.objectContaining({
[rowAction.id]: expect.objectContaining({
name: "action name",
}),
}),
})
)
})
2024-07-11 17:14:14 +02:00
it("throws Bad Request when trying to update by a non-existing id", async () => {
await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.update(
tableId,
generator.guid(),
createRowActionRequest(),
{ status: 400 }
)
})
it("throws Bad Request when trying to update by a via another table id", async () => {
const otherTable = await config.api.table.save(
setup.structures.basicTable()
)
await createRowAction(otherTable._id!, createRowActionRequest())
const action = await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.update(
otherTable._id!,
2024-07-11 17:16:14 +02:00
action.id,
2024-07-11 17:14:14 +02:00
createRowActionRequest(),
{ status: 400 }
)
})
2024-07-17 12:26:36 +02:00
it("can not use existing row action names (for the same table)", async () => {
const action1 = await createRowAction(tableId, createRowActionRequest())
const action2 = await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.update(
tableId,
action1.id,
{ name: action2.name },
{
status: 409,
body: {
message: "A row action with the same name already exists.",
},
}
)
})
2024-07-17 12:30:31 +02:00
it("does not throw with name conflicts for the same row action", async () => {
const action1 = await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.update(tableId, action1.id, {
name: action1.name,
})
})
2024-07-11 17:08:57 +02:00
})
2024-07-11 17:33:40 +02:00
describe("delete", () => {
2024-08-26 12:43:35 +02:00
unauthorisedTests((expectations, testConfig) =>
config.api.rowAction.delete(
tableId,
generator.guid(),
expectations,
testConfig
)
)
2024-07-11 17:33:40 +02:00
it("can delete existing actions", async () => {
const actions: RowActionResponse[] = []
for (const rowAction of createRowActionRequests(3)) {
actions.push(await createRowAction(tableId, rowAction))
}
const actionToDelete = _.sample(actions)!
await config.api.rowAction.delete(tableId, actionToDelete.id, {
status: 204,
})
expect(await config.api.rowAction.find(tableId)).toEqual(
expect.objectContaining({
actions: actions
.filter(a => a.id !== actionToDelete.id)
.reduce((acc, c) => ({ ...acc, [c.id]: expect.any(Object) }), {}),
})
)
})
it("throws Bad Request when trying to delete by a non-existing id", async () => {
await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.delete(tableId, generator.guid(), {
status: 400,
})
})
it("throws Bad Request when trying to delete by a via another table id", async () => {
const otherTable = await config.api.table.save(
setup.structures.basicTable()
)
await createRowAction(otherTable._id!, createRowActionRequest())
const action = await createRowAction(tableId, createRowActionRequest())
await config.api.rowAction.delete(otherTable._id!, action.id, {
status: 400,
})
})
2024-07-19 11:01:09 +02:00
it("deletes the linked automation", async () => {
const actions: RowActionResponse[] = []
for (const rowAction of createRowActionRequests(3)) {
actions.push(await createRowAction(tableId, rowAction))
}
const actionToDelete = _.sample(actions)!
await config.api.rowAction.delete(tableId, actionToDelete.id, {
status: 204,
})
await config.api.automation.get(actionToDelete.automationId, {
status: 404,
})
for (const action of actions.filter(a => a.id !== actionToDelete.id)) {
await config.api.automation.get(action.automationId, {
status: 200,
})
}
})
2024-07-11 17:33:40 +02:00
})
2024-08-26 12:33:56 +02:00
2024-08-26 13:21:34 +02:00
describe("set/unsetViewPermission", () => {
describe.each([
["setViewPermission", config.api.rowAction.setViewPermission],
["unsetViewPermission", config.api.rowAction.unsetViewPermission],
])("unauthorisedTests for %s", (__, delegateTest) => {
unauthorisedTests((expectations, testConfig) =>
delegateTest(
tableId,
generator.guid(),
generator.guid(),
expectations,
testConfig
)
2024-08-26 12:43:35 +02:00
)
2024-08-26 13:21:34 +02:00
})
2024-08-26 12:33:56 +02:00
2024-08-26 13:21:34 +02:00
let tableIdForDescribe: string
let actionId1: string, actionId2: string
let viewId1: string, viewId2: string
beforeAll(async () => {
tableIdForDescribe = tableId
2024-08-26 12:33:56 +02:00
for (const rowAction of createRowActionRequests(3)) {
await createRowAction(tableId, rowAction)
}
const persisted = await config.api.rowAction.find(tableId)
2024-08-26 13:21:34 +02:00
const actions = _.sampleSize(Object.keys(persisted.actions), 2)
actionId1 = actions[0]
actionId2 = actions[1]
viewId1 = (
await config.api.viewV2.create(
setup.structures.viewV2.createRequest(tableId)
)
).id
viewId2 = (
await config.api.viewV2.create(
setup.structures.viewV2.createRequest(tableId)
)
).id
})
2024-08-26 12:33:56 +02:00
2024-08-26 13:21:34 +02:00
beforeEach(() => {
// Hack to reuse tables for these given tests
tableId = tableIdForDescribe
})
2024-08-26 12:33:56 +02:00
2024-08-26 13:21:34 +02:00
it("can set permission views", async () => {
2024-08-29 16:11:05 +02:00
await config.api.rowAction.setViewPermission(tableId, viewId1, actionId1)
2024-08-26 12:33:56 +02:00
const action1Result = await config.api.rowAction.setViewPermission(
tableId,
viewId2,
2024-08-29 16:11:05 +02:00
actionId1
2024-08-26 12:33:56 +02:00
)
const action2Result = await config.api.rowAction.setViewPermission(
tableId,
viewId1,
2024-08-29 16:11:05 +02:00
actionId2
2024-08-26 12:33:56 +02:00
)
const expectedAction1 = expect.objectContaining({
allowedViews: [viewId1, viewId2],
})
const expectedAction2 = expect.objectContaining({
allowedViews: [viewId1],
})
const expectedActions = expect.objectContaining({
[actionId1]: expectedAction1,
[actionId2]: expectedAction2,
})
expect(action1Result).toEqual(expectedAction1)
expect(action2Result).toEqual(expectedAction2)
expect((await config.api.rowAction.find(tableId)).actions).toEqual(
expectedActions
)
})
it("can unset permission views", async () => {
const actionResult = await config.api.rowAction.unsetViewPermission(
tableId,
viewId1,
2024-08-29 16:11:05 +02:00
actionId1
2024-08-26 12:33:56 +02:00
)
const expectedAction = expect.objectContaining({
allowedViews: [viewId2],
})
expect(actionResult).toEqual(expectedAction)
expect(
2024-08-26 13:21:34 +02:00
(await config.api.rowAction.find(tableId)).actions[actionId1]
2024-08-26 12:33:56 +02:00
).toEqual(expectedAction)
})
2024-08-26 13:21:34 +02:00
it.each([
["setViewPermission", config.api.rowAction.setViewPermission],
["unsetViewPermission", config.api.rowAction.unsetViewPermission],
])(
"cannot update permission views for unexisting views (%s)",
async (__, delegateTest) => {
const viewId = generator.guid()
await delegateTest(tableId, viewId, actionId1, {
status: 400,
body: {
message: `View '${viewId}' not found in '${tableId}'`,
},
})
}
)
it.each([
["setViewPermission", config.api.rowAction.setViewPermission],
["unsetViewPermission", config.api.rowAction.unsetViewPermission],
])(
"cannot update permission views crossing table views (%s)",
async (__, delegateTest) => {
const anotherTable = await config.api.table.save(
setup.structures.basicTable()
)
const { id: viewId } = await config.api.viewV2.create(
setup.structures.viewV2.createRequest(anotherTable._id!)
)
await delegateTest(tableId, viewId, actionId1, {
status: 400,
body: {
message: `View '${viewId}' not found in '${tableId}'`,
},
})
}
)
2024-08-26 12:33:56 +02:00
})
2024-08-26 15:17:18 +02:00
describe("trigger", () => {
let row: Row
let rowAction: RowActionResponse
2024-08-26 16:12:39 +02:00
beforeEach(async () => {
row = await config.api.row.save(tableId, {})
rowAction = await createRowAction(tableId, createRowActionRequest())
await config.publish()
2024-08-26 17:26:59 +02:00
tk.travel(Date.now() + 100)
2024-08-26 16:12:39 +02:00
})
2024-08-26 17:26:59 +02:00
async function getAutomationLogs() {
const { data: automationLogs } = await config.doInContext(
config.getProdAppId(),
async () =>
automations.logs.logSearch({ startDate: new Date().toISOString() })
2024-08-26 15:17:18 +02:00
)
2024-08-26 17:26:59 +02:00
return automationLogs
}
2024-08-26 15:17:18 +02:00
it("can trigger an automation given valid data", async () => {
2024-08-26 16:12:39 +02:00
await config.api.rowAction.trigger(tableId, rowAction.id, {
2024-08-26 17:26:59 +02:00
rowId: row._id!,
2024-08-26 16:12:39 +02:00
})
2024-08-26 15:17:18 +02:00
2024-08-26 17:26:59 +02:00
const automationLogs = await getAutomationLogs()
2024-08-26 15:17:18 +02:00
expect(automationLogs).toEqual([
expect.objectContaining({
automationId: rowAction.automationId,
trigger: expect.objectContaining({
outputs: {
fields: {},
row: await config.api.row.get(tableId, row._id!),
table: await config.api.table.get(tableId),
},
}),
}),
])
})
2024-08-26 17:13:52 +02:00
it("rejects triggering from a non-allowed view", async () => {
const viewId = (
await config.api.viewV2.create(
setup.structures.viewV2.createRequest(tableId)
)
).id
2024-08-26 18:00:14 +02:00
await config.publish()
2024-08-26 17:13:52 +02:00
await config.api.rowAction.trigger(
viewId,
rowAction.id,
{
rowId: row._id!,
},
{
status: 403,
body: {
2024-08-26 17:26:59 +02:00
message: `Row action '${rowAction.id}' is not enabled for view '${viewId}'`,
2024-08-26 17:13:52 +02:00
},
}
)
2024-08-26 17:26:59 +02:00
const automationLogs = await getAutomationLogs()
expect(automationLogs).toEqual([])
})
it("triggers from an allowed view", async () => {
const viewId = (
await config.api.viewV2.create(
setup.structures.viewV2.createRequest(tableId)
)
).id
await config.api.rowAction.setViewPermission(
tableId,
viewId,
rowAction.id
2024-08-26 17:13:52 +02:00
)
2024-08-26 17:26:59 +02:00
2024-08-26 18:00:14 +02:00
await config.publish()
await config.api.rowAction.trigger(viewId, rowAction.id, {
2024-08-26 17:26:59 +02:00
rowId: row._id!,
})
const automationLogs = await getAutomationLogs()
2024-08-26 17:13:52 +02:00
expect(automationLogs).toEqual([
expect.objectContaining({
automationId: rowAction.automationId,
}),
])
})
2024-09-04 10:16:59 +02:00
2024-09-04 11:11:10 +02:00
describe("role permission checks", () => {
function createUser(role: string) {
return config.createUser({
admin: { global: false },
builder: {},
roles: { [config.getProdAppId()]: role },
})
}
2024-09-04 10:16:59 +02:00
2024-09-04 11:11:10 +02:00
function getRolesHigherThan(role: string) {
const result = Object.values(roles.BUILTIN_ROLE_IDS).filter(
r => r !== role && roles.lowerBuiltinRoleID(r, role) === role
2024-09-04 10:16:59 +02:00
)
2024-09-04 11:11:10 +02:00
return result
2024-09-04 10:16:59 +02:00
}
2024-09-04 11:11:10 +02:00
function getRolesLowerThan(role: string) {
const result = Object.values(roles.BUILTIN_ROLE_IDS).filter(
r => r !== role && roles.lowerBuiltinRoleID(r, role) === r
)
return result
}
const allowedRoleConfig = Object.values(roles.BUILTIN_ROLE_IDS).flatMap(
r => [r, ...getRolesLowerThan(r)].map(p => [r, p])
)
const disallowedRoleConfig = Object.values(
roles.BUILTIN_ROLE_IDS
).flatMap(r => getRolesHigherThan(r).map(p => [r, p]))
it.each(allowedRoleConfig)(
"allows triggering if the user has table read permission (user %s, table %s)",
async (userRole, resourcePermission) => {
await config.api.permission.add({
level: PermissionLevel.READ,
resourceId: tableId,
roleId: resourcePermission,
})
const normalUser = await createUser(userRole)
await config.withUser(normalUser, async () => {
await config.publish()
await config.api.rowAction.trigger(
tableId,
rowAction.id,
{
rowId: row._id!,
},
{ status: 200 }
)
})
}
)
it.each(disallowedRoleConfig)(
"rejects if the user does not have table read permission (user %s, table %s)",
async (userRole, resourcePermission) => {
await config.api.permission.add({
level: PermissionLevel.READ,
resourceId: tableId,
roleId: resourcePermission,
})
const normalUser = await createUser(userRole)
await config.withUser(normalUser, async () => {
await config.publish()
await config.api.rowAction.trigger(
tableId,
rowAction.id,
{
rowId: row._id!,
},
{
status: 403,
body: { message: "User does not have permission" },
}
)
const automationLogs = await getAutomationLogs()
expect(automationLogs).toBeEmpty()
})
}
)
})
2024-08-26 15:17:18 +02:00
})
2024-07-10 13:24:25 +02:00
})