Merge branch 'master' into Fix-user-access-roles-from-displaying-business
This commit is contained in:
commit
423f76950f
|
@ -117,9 +117,9 @@ jobs:
|
|||
- name: Test
|
||||
run: |
|
||||
if ${{ env.ONLY_AFFECTED_TASKS }}; then
|
||||
yarn test --ignore=@budibase/worker --ignore=@budibase/server --since=${{ env.NX_BASE_BRANCH }}
|
||||
yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server --since=${{ env.NX_BASE_BRANCH }}
|
||||
else
|
||||
yarn test --ignore=@budibase/worker --ignore=@budibase/server
|
||||
yarn test --ignore=@budibase/worker --ignore=@budibase/server --ignore @budibase/account-portal-server
|
||||
fi
|
||||
|
||||
test-worker:
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -42,7 +42,7 @@ services:
|
|||
couchdb-service:
|
||||
container_name: budi-couchdb3-dev
|
||||
restart: on-failure
|
||||
image: budibase/couchdb:v3.3.3
|
||||
image: budibase/couchdb:v3.3.3-sqs-v2.1.1
|
||||
environment:
|
||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||
- COUCHDB_USER=${COUCH_DB_USER}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"version": "2.31.8",
|
||||
"version": "2.32.0",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
"build:docker:single": "./scripts/build-single-image.sh",
|
||||
"build:docker:single:sqs": "./scripts/build-single-image-sqs.sh",
|
||||
"build:docker:dependencies": "docker build -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest ./hosting",
|
||||
"publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 --push ./hosting/couchdb",
|
||||
"publish:docker:couch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/couchdb/Dockerfile -t budibase/couchdb:latest -t budibase/couchdb:v3.3.3 -t budibase/couchdb:v3.3.3-sqs-v2.1.1 --push ./hosting/couchdb",
|
||||
"publish:docker:dependencies": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/dependencies/Dockerfile -t budibase/dependencies:latest -t budibase/dependencies:v3.2.1 --push ./hosting",
|
||||
"release:helm": "node scripts/releaseHelmChart",
|
||||
"env:multi:enable": "lerna run --stream env:multi:enable",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 048c37ecd921340614bf61a76a774aaa46176569
|
||||
Subproject commit 7899d07904d89d48954dd500da7b5dec32b781dd
|
|
@ -63,14 +63,25 @@ async function populateUsersFromDB(
|
|||
* If not present fallback to loading the user directly and re-caching.
|
||||
* @param userId the id of the user to get
|
||||
* @param tenantId the tenant of the user to get
|
||||
* @param email the email of the user to populate from account if needed
|
||||
* @param populateUser function to provide the user for re-caching. default to couch db
|
||||
* @returns
|
||||
*/
|
||||
export async function getUser(
|
||||
userId: string,
|
||||
tenantId?: string,
|
||||
populateUser?: (userId: string, tenantId: string) => Promise<User>
|
||||
) {
|
||||
export async function getUser({
|
||||
userId,
|
||||
tenantId,
|
||||
email,
|
||||
populateUser,
|
||||
}: {
|
||||
userId: string
|
||||
email?: string
|
||||
tenantId?: string
|
||||
populateUser?: (
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
email?: string
|
||||
) => Promise<User>
|
||||
}) {
|
||||
if (!populateUser) {
|
||||
populateUser = populateFromDB
|
||||
}
|
||||
|
@ -85,7 +96,7 @@ export async function getUser(
|
|||
// try cache
|
||||
let user: User = await client.get(userId)
|
||||
if (!user) {
|
||||
user = await populateUser(userId, tenantId)
|
||||
user = await populateUser(userId, tenantId, email)
|
||||
await client.store(userId, user, EXPIRY_SECONDS)
|
||||
}
|
||||
if (user && !user.tenantId && tenantId) {
|
||||
|
|
|
@ -3,6 +3,7 @@ export * as processors from "./processors"
|
|||
export * as analytics from "./analytics"
|
||||
export { default as identification } from "./identification"
|
||||
export * as backfillCache from "./backfill"
|
||||
export { publishEvent } from "./events"
|
||||
|
||||
import { processors } from "./processors"
|
||||
|
||||
|
|
|
@ -43,7 +43,11 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) {
|
|||
|
||||
async function checkApiKey(
|
||||
apiKey: string,
|
||||
populateUser?: (userId: string, tenantId: string) => Promise<User>
|
||||
populateUser?: (
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
email?: string
|
||||
) => Promise<User>
|
||||
) {
|
||||
// check both the primary and the fallback internal api keys
|
||||
// this allows for rotation
|
||||
|
@ -70,7 +74,11 @@ async function checkApiKey(
|
|||
if (userId) {
|
||||
return {
|
||||
valid: true,
|
||||
user: await getUser(userId, tenantId, populateUser),
|
||||
user: await getUser({
|
||||
userId,
|
||||
tenantId,
|
||||
populateUser,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
throw new InvalidAPIKeyError()
|
||||
|
@ -123,13 +131,18 @@ export default function (
|
|||
// getting session handles error checking (if session exists etc)
|
||||
session = await getSession(userId, sessionId)
|
||||
if (opts && opts.populateUser) {
|
||||
user = await getUser(
|
||||
user = await getUser({
|
||||
userId,
|
||||
session.tenantId,
|
||||
opts.populateUser(ctx)
|
||||
)
|
||||
tenantId: session.tenantId,
|
||||
email: session.email,
|
||||
populateUser: opts.populateUser(ctx),
|
||||
})
|
||||
} else {
|
||||
user = await getUser(userId, session.tenantId)
|
||||
user = await getUser({
|
||||
userId,
|
||||
tenantId: session.tenantId,
|
||||
email: session.email,
|
||||
})
|
||||
}
|
||||
// @ts-ignore
|
||||
user.csrfToken = session.csrfToken
|
||||
|
@ -148,7 +161,11 @@ export default function (
|
|||
}
|
||||
// this is an internal request, no user made it
|
||||
if (!authenticated && apiKey) {
|
||||
const populateUser = opts.populateUser ? opts.populateUser(ctx) : null
|
||||
const populateUser: (
|
||||
userId: string,
|
||||
tenantId: string,
|
||||
email?: string
|
||||
) => Promise<User> = opts.populateUser ? opts.populateUser(ctx) : null
|
||||
const { valid, user: foundUser } = await checkApiKey(
|
||||
apiKey,
|
||||
populateUser
|
||||
|
|
|
@ -862,6 +862,21 @@ class InternalBuilder {
|
|||
return withSchema
|
||||
}
|
||||
|
||||
private buildJsonField(field: string): string {
|
||||
const parts = field.split(".")
|
||||
let tableField: string, unaliased: string
|
||||
if (parts.length > 1) {
|
||||
const alias = parts.shift()!
|
||||
unaliased = parts.join(".")
|
||||
tableField = `${this.quote(alias)}.${this.quote(unaliased)}`
|
||||
} else {
|
||||
unaliased = parts.join(".")
|
||||
tableField = this.quote(unaliased)
|
||||
}
|
||||
const separator = this.client === SqlClient.ORACLE ? " VALUE " : ","
|
||||
return `'${unaliased}'${separator}${tableField}`
|
||||
}
|
||||
|
||||
addJsonRelationships(
|
||||
query: Knex.QueryBuilder,
|
||||
fromTable: string,
|
||||
|
@ -871,27 +886,6 @@ class InternalBuilder {
|
|||
const knex = this.knex
|
||||
const { resource, tableAliases: aliases, endpoint } = this.query
|
||||
const fields = resource?.fields || []
|
||||
const jsonField = (field: string) => {
|
||||
const parts = field.split(".")
|
||||
let tableField: string, unaliased: string
|
||||
if (parts.length > 1) {
|
||||
const alias = parts.shift()!
|
||||
unaliased = parts.join(".")
|
||||
tableField = `${this.quote(alias)}.${this.quote(unaliased)}`
|
||||
} else {
|
||||
unaliased = parts.join(".")
|
||||
tableField = this.quote(unaliased)
|
||||
}
|
||||
let separator = ","
|
||||
switch (sqlClient) {
|
||||
case SqlClient.ORACLE:
|
||||
separator = " VALUE "
|
||||
break
|
||||
case SqlClient.MS_SQL:
|
||||
separator = ":"
|
||||
}
|
||||
return `'${unaliased}'${separator}${tableField}`
|
||||
}
|
||||
for (let relationship of relationships) {
|
||||
const {
|
||||
tableName: toTable,
|
||||
|
@ -921,7 +915,7 @@ class InternalBuilder {
|
|||
)
|
||||
}
|
||||
const fieldList: string = relationshipFields
|
||||
.map(field => jsonField(field))
|
||||
.map(field => this.buildJsonField(field))
|
||||
.join(",")
|
||||
// SQL Server uses TOP - which performs a little differently to the normal LIMIT syntax
|
||||
// it reduces the result set rather than limiting how much data it filters over
|
||||
|
@ -1073,43 +1067,6 @@ class InternalBuilder {
|
|||
return query
|
||||
}
|
||||
|
||||
addRelationships(
|
||||
query: Knex.QueryBuilder,
|
||||
fromTable: string,
|
||||
relationships: RelationshipsJson[]
|
||||
): Knex.QueryBuilder {
|
||||
const tableSets: Record<string, [RelationshipsJson]> = {}
|
||||
// aggregate into table sets (all the same to tables)
|
||||
for (let relationship of relationships) {
|
||||
const keyObj: { toTable: string; throughTable: string | undefined } = {
|
||||
toTable: relationship.tableName,
|
||||
throughTable: undefined,
|
||||
}
|
||||
if (relationship.through) {
|
||||
keyObj.throughTable = relationship.through
|
||||
}
|
||||
const key = JSON.stringify(keyObj)
|
||||
if (tableSets[key]) {
|
||||
tableSets[key].push(relationship)
|
||||
} else {
|
||||
tableSets[key] = [relationship]
|
||||
}
|
||||
}
|
||||
for (let [key, relationships] of Object.entries(tableSets)) {
|
||||
const { toTable, throughTable } = JSON.parse(key)
|
||||
query = this.addJoin(
|
||||
query,
|
||||
{
|
||||
from: fromTable,
|
||||
to: toTable,
|
||||
through: throughTable,
|
||||
},
|
||||
relationships
|
||||
)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
qualifiedKnex(opts?: { alias?: string | boolean }): Knex.QueryBuilder {
|
||||
let alias = this.query.tableAliases?.[this.query.endpoint.entityId]
|
||||
if (opts?.alias === false) {
|
||||
|
@ -1193,8 +1150,7 @@ class InternalBuilder {
|
|||
if (!primary) {
|
||||
throw new Error("Primary key is required for upsert")
|
||||
}
|
||||
const ret = query.insert(parsedBody).onConflict(primary).merge()
|
||||
return ret
|
||||
return query.insert(parsedBody).onConflict(primary).merge()
|
||||
} else if (
|
||||
this.client === SqlClient.MS_SQL ||
|
||||
this.client === SqlClient.ORACLE
|
||||
|
@ -1253,12 +1209,29 @@ class InternalBuilder {
|
|||
if (!counting) {
|
||||
query = this.addSorting(query)
|
||||
}
|
||||
// handle joins
|
||||
if (relationships) {
|
||||
query = this.addJsonRelationships(query, tableName, relationships)
|
||||
}
|
||||
|
||||
return this.addFilters(query, filters, { relationship: true })
|
||||
query = this.addFilters(query, filters, { relationship: true })
|
||||
|
||||
// handle relationships with a CTE for all others
|
||||
if (relationships?.length) {
|
||||
const mainTable =
|
||||
this.query.tableAliases?.[this.query.endpoint.entityId] ||
|
||||
this.query.endpoint.entityId
|
||||
const cte = this.addSorting(
|
||||
this.knex
|
||||
.with("paginated", query)
|
||||
.select(this.generateSelectStatement())
|
||||
.from({
|
||||
[mainTable]: "paginated",
|
||||
})
|
||||
)
|
||||
// add JSON aggregations attached to the CTE
|
||||
return this.addJsonRelationships(cte, tableName, relationships)
|
||||
}
|
||||
// no relationships found - return query
|
||||
else {
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
update(opts: QueryOptions): Knex.QueryBuilder {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { it, expect, describe, vi } from "vitest"
|
|||
import AISettings from "./index.svelte"
|
||||
import { render } from "@testing-library/svelte"
|
||||
import { admin, licensing } from "stores/portal"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
vi.spyOn(notifications, "error").mockImplementation(vi.fn)
|
||||
vi.spyOn(notifications, "success").mockImplementation(vi.fn)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:20-slim
|
||||
FROM node:20-alpine
|
||||
|
||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-check="scripts/watchtower-hooks/pre-check.sh"
|
||||
LABEL com.centurylinklabs.watchtower.lifecycle.pre-update="scripts/watchtower-hooks/pre-update.sh"
|
||||
|
@ -15,37 +15,35 @@ ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
|||
ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
|
||||
ENV TOP_LEVEL_PATH=/
|
||||
|
||||
# handle node-gyp
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends g++ make python3 jq
|
||||
RUN yarn global add pm2
|
||||
# handle node-gyp and install postgres client for pg_dump utils
|
||||
RUN apk add --no-cache \
|
||||
g++ \
|
||||
make \
|
||||
python3 \
|
||||
jq \
|
||||
bash \
|
||||
postgresql-client \
|
||||
git
|
||||
|
||||
# Install postgres client for pg_dump utils
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install software-properties-common apt-transport-https curl gpg -y \
|
||||
&& curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \
|
||||
&& echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \
|
||||
&& apt update -y \
|
||||
&& apt install postgresql-client-15 -y \
|
||||
&& apt remove software-properties-common apt-transport-https curl gpg -y
|
||||
RUN yarn global add pm2
|
||||
|
||||
WORKDIR /
|
||||
|
||||
COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh
|
||||
RUN chmod +x ./scripts/removeWorkspaceDependencies.sh
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
COPY packages/server/package.json .
|
||||
COPY packages/server/dist/yarn.lock .
|
||||
|
||||
COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh
|
||||
RUN chmod +x ./scripts/removeWorkspaceDependencies.sh
|
||||
RUN ./scripts/removeWorkspaceDependencies.sh package.json
|
||||
RUN ./scripts/removeWorkspaceDependencies.sh package.json
|
||||
|
||||
# Install yarn packages with caching
|
||||
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production=true --network-timeout 1000000 \
|
||||
# Remove unneeded data from file system to reduce image size
|
||||
&& yarn cache clean && apt-get remove -y --purge --auto-remove g++ make python jq \
|
||||
&& yarn cache clean \
|
||||
&& apk del g++ make python3 jq \
|
||||
&& rm -rf /tmp/* /root/.node-gyp /usr/local/lib/node_modules/npm/node_modules/node-gyp
|
||||
|
||||
COPY packages/server/dist/ dist/
|
||||
|
@ -69,7 +67,7 @@ EXPOSE 4001
|
|||
# due to this causing yarn to stop installing dev dependencies
|
||||
# which are actually needed to get this environment up and running
|
||||
ENV NODE_ENV=production
|
||||
# this is required for isolated-vm to work on Node 20+
|
||||
# This is required for isolated-vm to work on Node 20+
|
||||
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||
ENV CLUSTER_MODE=${CLUSTER_MODE}
|
||||
ENV TOP_LEVEL_PATH=/app
|
||||
|
|
|
@ -145,35 +145,32 @@ export function basicProcessing({
|
|||
: typeof value === "string"
|
||||
? JSON.parse(value)
|
||||
: undefined
|
||||
if (array) {
|
||||
if (array && Array.isArray(array)) {
|
||||
thisRow[col] = array
|
||||
// make sure all of them have an _id
|
||||
if (Array.isArray(thisRow[col])) {
|
||||
const sortField =
|
||||
relatedTable.primaryDisplay || relatedTable.primary![0]!
|
||||
thisRow[col] = (thisRow[col] as Row[])
|
||||
.map(relatedRow =>
|
||||
basicProcessing({
|
||||
row: relatedRow,
|
||||
table: relatedTable,
|
||||
tables,
|
||||
isLinked: false,
|
||||
sqs,
|
||||
})
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aField = a?.[sortField],
|
||||
bField = b?.[sortField]
|
||||
if (!aField) {
|
||||
return 1
|
||||
} else if (!bField) {
|
||||
return -1
|
||||
}
|
||||
return aField.localeCompare
|
||||
? aField.localeCompare(bField)
|
||||
: aField - bField
|
||||
const sortField = relatedTable.primaryDisplay || relatedTable.primary![0]!
|
||||
thisRow[col] = (thisRow[col] as Row[])
|
||||
.map(relatedRow =>
|
||||
basicProcessing({
|
||||
row: relatedRow,
|
||||
table: relatedTable,
|
||||
tables,
|
||||
isLinked: false,
|
||||
sqs,
|
||||
})
|
||||
}
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aField = a?.[sortField],
|
||||
bField = b?.[sortField]
|
||||
if (!aField) {
|
||||
return 1
|
||||
} else if (!bField) {
|
||||
return -1
|
||||
}
|
||||
return aField.localeCompare
|
||||
? aField.localeCompare(bField)
|
||||
: aField - bField
|
||||
})
|
||||
}
|
||||
}
|
||||
return fixJsonTypes(thisRow, table)
|
||||
|
|
|
@ -832,12 +832,13 @@ describe.each(
|
|||
},
|
||||
})
|
||||
expect(res).toHaveLength(1)
|
||||
expect(res[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 2,
|
||||
name: "two",
|
||||
})
|
||||
)
|
||||
expect(res[0]).toEqual({
|
||||
id: 2,
|
||||
name: "two",
|
||||
// the use of table.* introduces the possibility of nulls being returned
|
||||
birthday: null,
|
||||
number: null,
|
||||
})
|
||||
})
|
||||
|
||||
// this parameter really only impacts SQL queries
|
||||
|
|
|
@ -2907,6 +2907,28 @@ describe.each([
|
|||
'Invalid body - "query.$and.conditions[1].$and.conditions" is required'
|
||||
)
|
||||
})
|
||||
|
||||
it("returns no rows when onEmptyFilter set to none", async () => {
|
||||
await expectSearch({
|
||||
query: {
|
||||
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
|
||||
$and: {
|
||||
conditions: [{ equal: { name: "" } }],
|
||||
},
|
||||
},
|
||||
}).toFindNothing()
|
||||
})
|
||||
|
||||
it("returns all rows when onEmptyFilter set to all", async () => {
|
||||
await expectSearch({
|
||||
query: {
|
||||
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
|
||||
$and: {
|
||||
conditions: [{ equal: { name: "" } }],
|
||||
},
|
||||
},
|
||||
}).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
|
||||
!isLucene &&
|
||||
|
@ -3035,5 +3057,27 @@ describe.each([
|
|||
},
|
||||
}).toContainExactly([{ age: 1, name: "Jane" }])
|
||||
})
|
||||
|
||||
it("returns no rows when onEmptyFilter set to none", async () => {
|
||||
await expectSearch({
|
||||
query: {
|
||||
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
|
||||
$or: {
|
||||
conditions: [{ equal: { name: "" } }],
|
||||
},
|
||||
},
|
||||
}).toFindNothing()
|
||||
})
|
||||
|
||||
it("returns all rows when onEmptyFilter set to all", async () => {
|
||||
await expectSearch({
|
||||
query: {
|
||||
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
|
||||
$or: {
|
||||
conditions: [{ equal: { name: "" } }],
|
||||
},
|
||||
},
|
||||
}).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
import * as automation from "../../index"
|
||||
import * as setup from "../utilities"
|
||||
import { Table, AutomationStatus } from "@budibase/types"
|
||||
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
|
||||
|
||||
describe("Branching automations", () => {
|
||||
let config = setup.getConfig(),
|
||||
table: Table
|
||||
|
||||
beforeEach(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
await config.createRow()
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
it("should run a multiple nested branching automation", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.serverLog({ text: "Starting automation" })
|
||||
.branch({
|
||||
topLevelBranch1: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1" }).branch({
|
||||
branch1: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1.1" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": true },
|
||||
},
|
||||
},
|
||||
branch2: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1.2" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": false },
|
||||
},
|
||||
},
|
||||
}),
|
||||
condition: {
|
||||
equal: { "steps.1.success": true },
|
||||
},
|
||||
},
|
||||
topLevelBranch2: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": false },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
expect(results.steps[3].outputs.status).toContain("branch1 branch taken")
|
||||
expect(results.steps[4].outputs.message).toContain("Branch 1.1")
|
||||
})
|
||||
|
||||
it("should execute correct branch based on string equality", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "String Equality Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "active" } })
|
||||
.branch({
|
||||
activeBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Active user" }),
|
||||
condition: {
|
||||
equal: { "trigger.fields.status": "active" },
|
||||
},
|
||||
},
|
||||
inactiveBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Inactive user" }),
|
||||
condition: {
|
||||
equal: { "trigger.fields.status": "inactive" },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
expect(results.steps[0].outputs.status).toContain(
|
||||
"activeBranch branch taken"
|
||||
)
|
||||
expect(results.steps[1].outputs.message).toContain("Active user")
|
||||
})
|
||||
|
||||
it("should handle multiple conditions with AND operator", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Multiple AND Conditions Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "active", role: "admin" } })
|
||||
.branch({
|
||||
activeAdminBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Active admin user" }),
|
||||
condition: {
|
||||
$and: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "active" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
otherBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Other user" }),
|
||||
condition: {
|
||||
notEqual: { "trigger.fields.status": "active" },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.steps[1].outputs.message).toContain("Active admin user")
|
||||
})
|
||||
|
||||
it("should handle multiple conditions with OR operator", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Multiple OR Conditions Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "test", role: "user" } })
|
||||
.branch({
|
||||
specialBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Special user" }),
|
||||
condition: {
|
||||
$or: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "test" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
regularBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Regular user" }),
|
||||
condition: {
|
||||
$and: {
|
||||
conditions: [
|
||||
{ notEqual: { "trigger.fields.status": "active" } },
|
||||
{ notEqual: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.steps[1].outputs.message).toContain("Special user")
|
||||
})
|
||||
|
||||
it("should stop the branch automation when no conditions are met", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Multiple OR Conditions Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "test", role: "user" } })
|
||||
.createRow({ row: { name: "Test", tableId: table._id } })
|
||||
.branch({
|
||||
specialBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Special user" }),
|
||||
condition: {
|
||||
$or: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "new" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
regularBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Regular user" }),
|
||||
condition: {
|
||||
$and: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "active" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.serverLog({ text: "Test" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[1].outputs.status).toEqual(
|
||||
AutomationStatus.NO_CONDITION_MET
|
||||
)
|
||||
expect(results.steps[2]).toBeUndefined()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,245 @@
|
|||
import * as automation from "../../index"
|
||||
import * as setup from "../utilities"
|
||||
import {
|
||||
Table,
|
||||
LoopStepType,
|
||||
CreateRowStepOutputs,
|
||||
ServerLogStepOutputs,
|
||||
} from "@budibase/types"
|
||||
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
|
||||
|
||||
describe("Loop automations", () => {
|
||||
let config = setup.getConfig(),
|
||||
table: Table
|
||||
|
||||
beforeEach(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
await config.createRow()
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
it("should run an automation with a trigger, loop, and create row step", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.createRow({
|
||||
row: {
|
||||
name: "Item {{ loop.currentItem }}",
|
||||
description: "Created from loop",
|
||||
tableId: table._id,
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.trigger).toBeDefined()
|
||||
expect(results.steps).toHaveLength(1)
|
||||
|
||||
expect(results.steps[0].outputs.iterations).toBe(3)
|
||||
expect(results.steps[0].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[0].outputs.items.forEach((output: any, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
row: {
|
||||
name: `Item ${index + 1}`,
|
||||
description: "Created from loop",
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should run an automation where a loop step is between two normal steps to ensure context correctness", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.queryRows({
|
||||
tableId: table._id!,
|
||||
})
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.1.rows.0._id}}" })
|
||||
.run()
|
||||
|
||||
results.steps[1].outputs.items.forEach(
|
||||
(output: ServerLogStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
})
|
||||
expect(output.message).toContain(`Message ${index + 1}`)
|
||||
}
|
||||
)
|
||||
|
||||
expect(results.steps[2].outputs.message).toContain("ro_ta")
|
||||
})
|
||||
|
||||
it("if an incorrect type is passed to the loop it should return an error", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Loop error",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: "1, 2, 3",
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[0].outputs).toEqual({
|
||||
success: false,
|
||||
status: "INCORRECT_TYPE",
|
||||
})
|
||||
})
|
||||
|
||||
it("ensure the loop stops if the failure condition is reached", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Loop error",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: ["test", "test2", "test3"],
|
||||
failure: "test2",
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[0].outputs).toEqual(
|
||||
expect.objectContaining({
|
||||
status: "FAILURE_CONDITION_MET",
|
||||
success: false,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should run an automation where a loop is successfully run twice", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.createRow({
|
||||
row: {
|
||||
name: "Item {{ loop.currentItem }}",
|
||||
description: "Created from loop",
|
||||
tableId: table._id,
|
||||
},
|
||||
})
|
||||
.loop({
|
||||
option: LoopStepType.STRING,
|
||||
binding: "Message 1,Message 2,Message 3",
|
||||
})
|
||||
.serverLog({ text: "{{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.trigger).toBeDefined()
|
||||
expect(results.steps).toHaveLength(2)
|
||||
|
||||
expect(results.steps[0].outputs.iterations).toBe(3)
|
||||
expect(results.steps[0].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[0].outputs.items.forEach(
|
||||
(output: CreateRowStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
row: {
|
||||
name: `Item ${index + 1}`,
|
||||
description: "Created from loop",
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
expect(results.steps[1].outputs.iterations).toBe(3)
|
||||
expect(results.steps[1].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[1].outputs.items.forEach(
|
||||
(output: ServerLogStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
})
|
||||
expect(output.message).toContain(`Message ${index + 1}`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should run an automation where a loop is used twice to ensure context correctness further down the tree", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.1.iterations}}" })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "{{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.3.iterations}}" })
|
||||
.run()
|
||||
|
||||
// We want to ensure that bindings are corr
|
||||
expect(results.steps[1].outputs.message).toContain("- 3")
|
||||
expect(results.steps[3].outputs.message).toContain("- 3")
|
||||
})
|
||||
})
|
|
@ -1,397 +1,19 @@
|
|||
import * as automation from "../../index"
|
||||
import * as setup from "../utilities"
|
||||
import {
|
||||
Table,
|
||||
LoopStepType,
|
||||
CreateRowStepOutputs,
|
||||
ServerLogStepOutputs,
|
||||
FieldType,
|
||||
} from "@budibase/types"
|
||||
import { LoopStepType, FieldType } from "@budibase/types"
|
||||
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
|
||||
import { DatabaseName } from "../../../integrations/tests/utils"
|
||||
|
||||
describe("Automation Scenarios", () => {
|
||||
let config = setup.getConfig(),
|
||||
table: Table
|
||||
let config = setup.getConfig()
|
||||
|
||||
beforeEach(async () => {
|
||||
await automation.init()
|
||||
await config.init()
|
||||
table = await config.createTable()
|
||||
await config.createRow()
|
||||
})
|
||||
|
||||
afterAll(setup.afterAll)
|
||||
|
||||
describe("Branching automations", () => {
|
||||
it("should run a multiple nested branching automation", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.serverLog({ text: "Starting automation" })
|
||||
.branch({
|
||||
topLevelBranch1: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1" }).branch({
|
||||
branch1: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1.1" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": true },
|
||||
},
|
||||
},
|
||||
branch2: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Branch 1.2" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": false },
|
||||
},
|
||||
},
|
||||
}),
|
||||
condition: {
|
||||
equal: { "steps.1.success": true },
|
||||
},
|
||||
},
|
||||
topLevelBranch2: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Branch 2" }),
|
||||
condition: {
|
||||
equal: { "steps.1.success": false },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
expect(results.steps[3].outputs.status).toContain("branch1 branch taken")
|
||||
expect(results.steps[4].outputs.message).toContain("Branch 1.1")
|
||||
})
|
||||
|
||||
it("should execute correct branch based on string equality", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "String Equality Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "active" } })
|
||||
.branch({
|
||||
activeBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Active user" }),
|
||||
condition: {
|
||||
equal: { "trigger.fields.status": "active" },
|
||||
},
|
||||
},
|
||||
inactiveBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Inactive user" }),
|
||||
condition: {
|
||||
equal: { "trigger.fields.status": "inactive" },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
expect(results.steps[0].outputs.status).toContain(
|
||||
"activeBranch branch taken"
|
||||
)
|
||||
expect(results.steps[1].outputs.message).toContain("Active user")
|
||||
})
|
||||
|
||||
it("should handle multiple conditions with AND operator", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Multiple AND Conditions Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "active", role: "admin" } })
|
||||
.branch({
|
||||
activeAdminBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Active admin user" }),
|
||||
condition: {
|
||||
$and: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "active" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
otherBranch: {
|
||||
steps: stepBuilder => stepBuilder.serverLog({ text: "Other user" }),
|
||||
condition: {
|
||||
notEqual: { "trigger.fields.status": "active" },
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.steps[1].outputs.message).toContain("Active admin user")
|
||||
})
|
||||
|
||||
it("should handle multiple conditions with OR operator", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Multiple OR Conditions Branching",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: { status: "test", role: "user" } })
|
||||
.branch({
|
||||
specialBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Special user" }),
|
||||
condition: {
|
||||
$or: {
|
||||
conditions: [
|
||||
{ equal: { "trigger.fields.status": "test" } },
|
||||
{ equal: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
regularBranch: {
|
||||
steps: stepBuilder =>
|
||||
stepBuilder.serverLog({ text: "Regular user" }),
|
||||
condition: {
|
||||
$and: {
|
||||
conditions: [
|
||||
{ notEqual: { "trigger.fields.status": "active" } },
|
||||
{ notEqual: { "trigger.fields.role": "admin" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.steps[1].outputs.message).toContain("Special user")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Loop automations", () => {
|
||||
it("should run an automation with a trigger, loop, and create row step", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.createRow({
|
||||
row: {
|
||||
name: "Item {{ loop.currentItem }}",
|
||||
description: "Created from loop",
|
||||
tableId: table._id,
|
||||
},
|
||||
})
|
||||
.run()
|
||||
|
||||
expect(results.trigger).toBeDefined()
|
||||
expect(results.steps).toHaveLength(1)
|
||||
|
||||
expect(results.steps[0].outputs.iterations).toBe(3)
|
||||
expect(results.steps[0].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[0].outputs.items.forEach((output: any, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
row: {
|
||||
name: `Item ${index + 1}`,
|
||||
description: "Created from loop",
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should run an automation where a loop step is between two normal steps to ensure context correctness", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.queryRows({
|
||||
tableId: table._id!,
|
||||
})
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.1.rows.0._id}}" })
|
||||
.run()
|
||||
|
||||
results.steps[1].outputs.items.forEach(
|
||||
(output: ServerLogStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
})
|
||||
expect(output.message).toContain(`Message ${index + 1}`)
|
||||
}
|
||||
)
|
||||
|
||||
expect(results.steps[2].outputs.message).toContain("ro_ta")
|
||||
})
|
||||
|
||||
it("if an incorrect type is passed to the loop it should return an error", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Loop error",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: "1, 2, 3",
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[0].outputs).toEqual({
|
||||
success: false,
|
||||
status: "INCORRECT_TYPE",
|
||||
})
|
||||
})
|
||||
|
||||
it("ensure the loop stops if the failure condition is reached", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Loop error",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: ["test", "test2", "test3"],
|
||||
failure: "test2",
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.steps[0].outputs).toEqual(
|
||||
expect.objectContaining({
|
||||
status: "FAILURE_CONDITION_MET",
|
||||
success: false,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should run an automation where a loop is successfully run twice", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.rowSaved(
|
||||
{ tableId: table._id! },
|
||||
{
|
||||
row: {
|
||||
name: "Trigger Row",
|
||||
description: "This row triggers the automation",
|
||||
},
|
||||
id: "1234",
|
||||
revision: "1",
|
||||
}
|
||||
)
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.createRow({
|
||||
row: {
|
||||
name: "Item {{ loop.currentItem }}",
|
||||
description: "Created from loop",
|
||||
tableId: table._id,
|
||||
},
|
||||
})
|
||||
.loop({
|
||||
option: LoopStepType.STRING,
|
||||
binding: "Message 1,Message 2,Message 3",
|
||||
})
|
||||
.serverLog({ text: "{{loop.currentItem}}" })
|
||||
.run()
|
||||
|
||||
expect(results.trigger).toBeDefined()
|
||||
expect(results.steps).toHaveLength(2)
|
||||
|
||||
expect(results.steps[0].outputs.iterations).toBe(3)
|
||||
expect(results.steps[0].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[0].outputs.items.forEach(
|
||||
(output: CreateRowStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
row: {
|
||||
name: `Item ${index + 1}`,
|
||||
description: "Created from loop",
|
||||
},
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
expect(results.steps[1].outputs.iterations).toBe(3)
|
||||
expect(results.steps[1].outputs.items).toHaveLength(3)
|
||||
|
||||
results.steps[1].outputs.items.forEach(
|
||||
(output: ServerLogStepOutputs, index: number) => {
|
||||
expect(output).toMatchObject({
|
||||
success: true,
|
||||
})
|
||||
expect(output.message).toContain(`Message ${index + 1}`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should run an automation where a loop is used twice to ensure context correctness further down the tree", async () => {
|
||||
const builder = createAutomationBuilder({
|
||||
name: "Test Trigger with Loop and Create Row",
|
||||
})
|
||||
|
||||
const results = await builder
|
||||
.appAction({ fields: {} })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "Message {{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.1.iterations}}" })
|
||||
.loop({
|
||||
option: LoopStepType.ARRAY,
|
||||
binding: [1, 2, 3],
|
||||
})
|
||||
.serverLog({ text: "{{loop.currentItem}}" })
|
||||
.serverLog({ text: "{{steps.3.iterations}}" })
|
||||
.run()
|
||||
|
||||
// We want to ensure that bindings are corr
|
||||
expect(results.steps[1].outputs.message).toContain("- 3")
|
||||
expect(results.steps[3].outputs.message).toContain("- 3")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Row Automations", () => {
|
||||
it("should trigger an automation which then creates a row", async () => {
|
||||
const table = await config.createTable()
|
||||
|
|
|
@ -161,16 +161,16 @@ describe("SQL query builder", () => {
|
|||
it("should add the schema to the LEFT JOIN", () => {
|
||||
const query = sql._query(generateRelationshipJson({ schema: "production" }))
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, limit],
|
||||
sql: `select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "production"."products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $1) as "products") as "products" from "production"."brands" order by "test"."id" asc limit $2`,
|
||||
bindings: [limit, relationshipLimit],
|
||||
sql: `with "paginated" as (select "brands".* from "production"."brands" order by "test"."id" asc limit $1) select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "production"."products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc`,
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle if the schema is not present when doing a LEFT JOIN", () => {
|
||||
const query = sql._query(generateRelationshipJson())
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, limit],
|
||||
sql: `select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $1) as "products") as "products" from "brands" order by "test"."id" asc limit $2`,
|
||||
bindings: [limit, relationshipLimit],
|
||||
sql: `with "paginated" as (select "brands".* from "brands" order by "test"."id" asc limit $1) select "brands".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name",'brand_id',"products"."brand_id")) from (select "products".* from "products" as "products" where "products"."brand_id" = "brands"."brand_id" order by "products"."brand_id" asc limit $2) as "products") as "products" from "paginated" as "brands" order by "test"."id" asc`,
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -179,8 +179,8 @@ describe("SQL query builder", () => {
|
|||
generateManyRelationshipJson({ schema: "production" })
|
||||
)
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, limit],
|
||||
sql: `select "stores".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" inner join "production"."stocks" as "stocks" on "products"."product_id" = "stocks"."product_id" where "stocks"."store_id" = "stores"."store_id" order by "products"."product_id" asc limit $1) as "products") as "products" from "production"."stores" order by "test"."id" asc limit $2`,
|
||||
bindings: [limit, relationshipLimit],
|
||||
sql: `with "paginated" as (select "stores".* from "production"."stores" order by "test"."id" asc limit $1) select "stores".*, (select json_agg(json_build_object('product_id',"products"."product_id",'product_name',"products"."product_name")) from (select "products".* from "production"."products" as "products" inner join "production"."stocks" as "stocks" on "products"."product_id" = "stocks"."product_id" where "stocks"."store_id" = "stores"."store_id" order by "products"."product_id" asc limit $2) as "products") as "products" from "paginated" as "stores" order by "test"."id" asc`,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ describe("Captures of real examples", () => {
|
|||
queryJson
|
||||
)
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, relationshipLimit, primaryLimit],
|
||||
bindings: [primaryLimit, relationshipLimit, relationshipLimit],
|
||||
sql: expect.stringContaining(
|
||||
multiline(
|
||||
`select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid",'executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid")`
|
||||
|
@ -75,11 +75,11 @@ describe("Captures of real examples", () => {
|
|||
queryJson
|
||||
)
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, "assembling", primaryLimit],
|
||||
bindings: ["assembling", primaryLimit, relationshipLimit],
|
||||
sql: expect.stringContaining(
|
||||
multiline(
|
||||
`where exists (select 1 from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid"
|
||||
where "c"."productid" = "a"."productid" and COALESCE("b"."taskname" = $2, FALSE)`
|
||||
`where exists (select 1 from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" where "c"."productid" = "a"."productid"
|
||||
and COALESCE("b"."taskname" = $1, FALSE)`
|
||||
)
|
||||
),
|
||||
})
|
||||
|
@ -91,12 +91,13 @@ describe("Captures of real examples", () => {
|
|||
queryJson
|
||||
)
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, primaryLimit],
|
||||
bindings: [primaryLimit, relationshipLimit],
|
||||
sql: expect.stringContaining(
|
||||
multiline(
|
||||
`select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid"))
|
||||
from (select "b".* from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid"
|
||||
where "c"."productid" = "a"."productid" order by "b"."taskid" asc limit $1`
|
||||
`with "paginated" as (select "a".* from "products" as "a" order by "a"."productname" asc nulls first, "a"."productid" asc limit $1)
|
||||
select "a".*, (select json_agg(json_build_object('executorid',"b"."executorid",'taskname',"b"."taskname",'taskid',"b"."taskid",'completed',"b"."completed",'qaid',"b"."qaid"))
|
||||
from (select "b".* from "tasks" as "b" inner join "products_tasks" as "c" on "b"."taskid" = "c"."taskid" where "c"."productid" = "a"."productid" order by "b"."taskid" asc limit $2) as "b") as "tasks"
|
||||
from "paginated" as "a" order by "a"."productname" asc nulls first, "a"."productid" asc`
|
||||
)
|
||||
),
|
||||
})
|
||||
|
@ -109,12 +110,12 @@ describe("Captures of real examples", () => {
|
|||
queryJson
|
||||
)
|
||||
expect(query).toEqual({
|
||||
bindings: [relationshipLimit, ...filters, relationshipLimit],
|
||||
bindings: [...filters, relationshipLimit, relationshipLimit],
|
||||
sql: multiline(
|
||||
`select "a".*, (select json_agg(json_build_object('productname',"b"."productname",'productid',"b"."productid"))
|
||||
`with "paginated" as (select "a".* from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3)
|
||||
select "a".*, (select json_agg(json_build_object('productname',"b"."productname",'productid',"b"."productid"))
|
||||
from (select "b".* from "products" as "b" inner join "products_tasks" as "c" on "b"."productid" = "c"."productid"
|
||||
where "c"."taskid" = "a"."taskid" order by "b"."productid" asc limit $1) as "b") as "products"
|
||||
from "tasks" as "a" where "a"."taskid" in ($2, $3) order by "a"."taskid" asc limit $4`
|
||||
where "c"."taskid" = "a"."taskid" order by "b"."productid" asc limit $4) as "b") as "products" from "paginated" as "a" order by "a"."taskid" asc`
|
||||
),
|
||||
})
|
||||
})
|
||||
|
@ -132,18 +133,18 @@ describe("Captures of real examples", () => {
|
|||
|
||||
expect(query).toEqual({
|
||||
bindings: [
|
||||
relationshipLimit,
|
||||
relationshipLimit,
|
||||
relationshipLimit,
|
||||
rangeValue.low,
|
||||
rangeValue.high,
|
||||
equalValue,
|
||||
notEqualsValue,
|
||||
primaryLimit,
|
||||
relationshipLimit,
|
||||
relationshipLimit,
|
||||
relationshipLimit,
|
||||
],
|
||||
sql: expect.stringContaining(
|
||||
multiline(
|
||||
`where exists (select 1 from "persons" as "c" where "c"."personid" = "a"."executorid" and "c"."year" between $4 and $5)`
|
||||
`where exists (select 1 from "persons" as "c" where "c"."personid" = "a"."executorid" and "c"."year" between $1 and $2)`
|
||||
)
|
||||
),
|
||||
})
|
||||
|
|
|
@ -333,6 +333,7 @@ export default class TestConfiguration {
|
|||
sessionId: this.sessionIdForUser(_id),
|
||||
tenantId: this.getTenantId(),
|
||||
csrfToken: this.csrfToken,
|
||||
email,
|
||||
})
|
||||
const resp = await db.put(user)
|
||||
await cache.user.invalidateUser(_id)
|
||||
|
@ -396,16 +397,17 @@ export default class TestConfiguration {
|
|||
}
|
||||
// make sure the user exists in the global DB
|
||||
if (roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
|
||||
await this.globalUser({
|
||||
const user = await this.globalUser({
|
||||
_id: userId,
|
||||
builder: { global: builder },
|
||||
roles: { [appId]: roleId || roles.BUILTIN_ROLE_IDS.BASIC },
|
||||
})
|
||||
await sessions.createASession(userId, {
|
||||
sessionId: this.sessionIdForUser(userId),
|
||||
tenantId: this.getTenantId(),
|
||||
email: user.email,
|
||||
})
|
||||
}
|
||||
await sessions.createASession(userId, {
|
||||
sessionId: this.sessionIdForUser(userId),
|
||||
tenantId: this.getTenantId(),
|
||||
})
|
||||
// have to fake this
|
||||
const authObj = {
|
||||
userId,
|
||||
|
|
|
@ -323,7 +323,9 @@ class Orchestrator {
|
|||
} else if (step.stepId === AutomationActionStepId.LOOP) {
|
||||
stepIndex = await this.executeLoopStep(step, steps, stepIndex)
|
||||
} else {
|
||||
await this.executeStep(step)
|
||||
if (!this.stopped) {
|
||||
await this.executeStep(step)
|
||||
}
|
||||
stepIndex++
|
||||
}
|
||||
}
|
||||
|
@ -465,7 +467,7 @@ class Orchestrator {
|
|||
for (const branch of branches) {
|
||||
const condition = await this.evaluateBranchCondition(branch.condition)
|
||||
if (condition) {
|
||||
let branchStatus = {
|
||||
const branchStatus = {
|
||||
status: `${branch.name} branch taken`,
|
||||
success: true,
|
||||
}
|
||||
|
@ -480,9 +482,20 @@ class Orchestrator {
|
|||
|
||||
const branchSteps = children?.[branch.name] || []
|
||||
await this.executeSteps(branchSteps)
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.stopped = true
|
||||
this.updateExecutionOutput(
|
||||
branchStep.id,
|
||||
branchStep.stepId,
|
||||
branchStep.inputs,
|
||||
{
|
||||
success: false,
|
||||
status: AutomationStatus.NO_CONDITION_MET,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private async evaluateBranchCondition(
|
||||
|
|
|
@ -247,7 +247,9 @@ class QueryRunner {
|
|||
if (!resp.err) {
|
||||
const globalUserId = getGlobalIDFromUserMetadataID(_id)
|
||||
await auth.updateUserOAuth(globalUserId, resp)
|
||||
this.ctx.user = await cache.user.getUser(globalUserId)
|
||||
this.ctx.user = await cache.user.getUser({
|
||||
userId: globalUserId,
|
||||
})
|
||||
} else {
|
||||
// In this event the user may have oAuth issues that
|
||||
// could require re-authenticating with their provider.
|
||||
|
|
|
@ -77,7 +77,9 @@ export async function getCachedSelf(
|
|||
): Promise<ContextUser> {
|
||||
// this has to be tenant aware, can't depend on the context to find it out
|
||||
// running some middlewares before the tenancy causes context to break
|
||||
const user = await cache.user.getUser(ctx.user?._id!)
|
||||
const user = await cache.user.getUser({
|
||||
userId: ctx.user?._id!,
|
||||
})
|
||||
return processUser(user, { appId })
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,9 @@ export async function processInputBBReference(
|
|||
}
|
||||
|
||||
try {
|
||||
await cache.user.getUser(id)
|
||||
await cache.user.getUser({
|
||||
userId: id,
|
||||
})
|
||||
return id
|
||||
} catch (e: any) {
|
||||
if (e.statusCode === 404) {
|
||||
|
@ -125,7 +127,9 @@ export async function processOutputBBReference(
|
|||
case BBReferenceFieldSubType.USER: {
|
||||
let user
|
||||
try {
|
||||
user = await cache.user.getUser(value as string)
|
||||
user = await cache.user.getUser({
|
||||
userId: value as string,
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.statusCode !== 404) {
|
||||
throw err
|
||||
|
|
|
@ -110,7 +110,9 @@ async function processDefaultValues(table: Table, row: Row) {
|
|||
|
||||
const identity = context.getIdentity()
|
||||
if (identity?._id && identity.type === IdentityType.USER) {
|
||||
const user = await cache.user.getUser(identity._id)
|
||||
const user = await cache.user.getUser({
|
||||
userId: identity._id,
|
||||
})
|
||||
delete user.password
|
||||
|
||||
ctx["Current User"] = user
|
||||
|
|
|
@ -74,7 +74,9 @@ describe("bbReferenceProcessor", () => {
|
|||
|
||||
expect(result).toEqual(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith({
|
||||
userId,
|
||||
})
|
||||
})
|
||||
|
||||
it("throws an error given an invalid id", async () => {
|
||||
|
@ -98,7 +100,9 @@ describe("bbReferenceProcessor", () => {
|
|||
|
||||
expect(result).toEqual(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith({
|
||||
userId,
|
||||
})
|
||||
})
|
||||
|
||||
it("empty strings will return null", async () => {
|
||||
|
@ -243,7 +247,9 @@ describe("bbReferenceProcessor", () => {
|
|||
lastName: user.lastName,
|
||||
})
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith({
|
||||
userId,
|
||||
})
|
||||
})
|
||||
|
||||
it("returns undefined given an unexisting user", async () => {
|
||||
|
@ -255,7 +261,9 @@ describe("bbReferenceProcessor", () => {
|
|||
|
||||
expect(result).toBeUndefined()
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
|
||||
expect(cacheGetUserSpy).toHaveBeenCalledWith({
|
||||
userId,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -27,6 +27,12 @@ import { isPlainObject, isEmpty } from "lodash"
|
|||
import { decodeNonAscii } from "./helpers/schema"
|
||||
|
||||
const HBS_REGEX = /{{([^{].*?)}}/g
|
||||
const LOGICAL_OPERATORS = Object.values(LogicalOperator)
|
||||
const SEARCH_OPERATORS = [
|
||||
...Object.values(BasicOperator),
|
||||
...Object.values(ArrayOperator),
|
||||
...Object.values(RangeOperator),
|
||||
]
|
||||
|
||||
/**
|
||||
* Returns the valid operator options for a certain data type
|
||||
|
@ -117,7 +123,7 @@ export function recurseLogicalOperators(
|
|||
filters: SearchFilters,
|
||||
fn: (f: SearchFilters) => SearchFilters
|
||||
) {
|
||||
for (const logical of Object.values(LogicalOperator)) {
|
||||
for (const logical of LOGICAL_OPERATORS) {
|
||||
if (filters[logical]) {
|
||||
filters[logical]!.conditions = filters[logical]!.conditions.map(
|
||||
condition => fn(condition)
|
||||
|
@ -135,7 +141,7 @@ export function recurseSearchFilters(
|
|||
filters = processFn(filters)
|
||||
|
||||
// Recurse through logical operators
|
||||
for (const logical of Object.values(LogicalOperator)) {
|
||||
for (const logical of LOGICAL_OPERATORS) {
|
||||
if (filters[logical]) {
|
||||
filters[logical]!.conditions = filters[logical]!.conditions.map(
|
||||
condition => recurseSearchFilters(condition, processFn)
|
||||
|
@ -773,12 +779,16 @@ export function runQuery<T extends Record<string, any>>(
|
|||
return filterFunctions[key as SearchFilterOperator]?.(doc) ?? false
|
||||
})
|
||||
|
||||
if (query.allOr) {
|
||||
// there are no filters - logical operators can cover this up
|
||||
if (!hasFilters(query)) {
|
||||
return true
|
||||
} else if (query.allOr) {
|
||||
return results.some(result => result === true)
|
||||
} else {
|
||||
return results.every(result => result === true)
|
||||
}
|
||||
}
|
||||
|
||||
return docs.filter(docMatch)
|
||||
}
|
||||
|
||||
|
@ -841,14 +851,33 @@ export const hasFilters = (query?: SearchFilters) => {
|
|||
if (!query) {
|
||||
return false
|
||||
}
|
||||
const skipped = ["allOr", "onEmptyFilter"]
|
||||
for (let [key, value] of Object.entries(query)) {
|
||||
if (skipped.includes(key) || typeof value !== "object") {
|
||||
continue
|
||||
const check = (filters: SearchFilters): boolean => {
|
||||
for (const logical of LOGICAL_OPERATORS) {
|
||||
if (filters[logical]) {
|
||||
for (const condition of filters[logical]?.conditions || []) {
|
||||
const result = check(condition)
|
||||
if (result) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(value || {}).length !== 0) {
|
||||
return true
|
||||
for (const search of SEARCH_OPERATORS) {
|
||||
const searchValue = filters[search]
|
||||
if (!searchValue || typeof searchValue !== "object") {
|
||||
continue
|
||||
}
|
||||
const filtered = Object.entries(searchValue).filter(entry => {
|
||||
const valueDefined =
|
||||
entry[1] !== undefined || entry[1] !== null || entry[1] !== ""
|
||||
// not empty is an edge case, null is allowed for it - this is covered by test cases
|
||||
return search === BasicOperator.NOT_EMPTY || valueDefined
|
||||
})
|
||||
if (filtered.length !== 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return check(query)
|
||||
}
|
||||
|
|
|
@ -179,6 +179,7 @@ export enum AutomationStatus {
|
|||
ERROR = "error",
|
||||
STOPPED = "stopped",
|
||||
STOPPED_ERROR = "stopped_error",
|
||||
NO_CONDITION_MET = "No branch condition met",
|
||||
}
|
||||
|
||||
export enum AutomationStoppedReason {
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface AuthToken {
|
|||
export interface CreateSession {
|
||||
sessionId: string
|
||||
tenantId: string
|
||||
email: string
|
||||
csrfToken?: string
|
||||
hosting?: Hosting
|
||||
}
|
||||
|
|
|
@ -19,12 +19,17 @@ import { EmailTemplatePurpose } from "../../constants"
|
|||
export async function loginUser(user: User) {
|
||||
const sessionId = coreUtils.newid()
|
||||
const tenantId = tenancy.getTenantId()
|
||||
await sessions.createASession(user._id!, { sessionId, tenantId })
|
||||
await sessions.createASession(user._id!, {
|
||||
sessionId,
|
||||
tenantId,
|
||||
email: user.email,
|
||||
})
|
||||
return jwt.sign(
|
||||
{
|
||||
userId: user._id,
|
||||
sessionId,
|
||||
tenantId,
|
||||
email: user.email,
|
||||
},
|
||||
coreEnv.JWT_SECRET!
|
||||
)
|
||||
|
|
|
@ -170,19 +170,26 @@ class TestConfiguration {
|
|||
async _createSession({
|
||||
userId,
|
||||
tenantId,
|
||||
email,
|
||||
}: {
|
||||
userId: string
|
||||
tenantId: string
|
||||
email: string
|
||||
}) {
|
||||
await sessions.createASession(userId!, {
|
||||
sessionId: "sessionid",
|
||||
tenantId: tenantId,
|
||||
tenantId,
|
||||
csrfToken: CSRF_TOKEN,
|
||||
email,
|
||||
})
|
||||
}
|
||||
|
||||
async createSession(user: User) {
|
||||
return this._createSession({ userId: user._id!, tenantId: user.tenantId })
|
||||
return this._createSession({
|
||||
userId: user._id!,
|
||||
tenantId: user.tenantId,
|
||||
email: user.email,
|
||||
})
|
||||
}
|
||||
|
||||
cookieHeader(cookies: any) {
|
||||
|
|
Loading…
Reference in New Issue