Merge pull request #13603 from Budibase/budi-8123/bbreference-process-single

Clean BBReferenceProcessor code
This commit is contained in:
Adria Navarro 2024-05-03 16:49:26 +02:00 committed by GitHub
commit f44d8442ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 403 additions and 223 deletions

View File

@ -104,7 +104,10 @@ export function basicProcessing({
export function fixArrayTypes(row: Row, table: Table) { export function fixArrayTypes(row: Row, table: Table) {
for (let [fieldName, schema] of Object.entries(table.schema)) { for (let [fieldName, schema] of Object.entries(table.schema)) {
if (schema.type === FieldType.ARRAY && typeof row[fieldName] === "string") { if (
[FieldType.ARRAY, FieldType.BB_REFERENCE].includes(schema.type) &&
typeof row[fieldName] === "string"
) {
try { try {
row[fieldName] = JSON.parse(row[fieldName]) row[fieldName] = JSON.parse(row[fieldName])
} catch (err) { } catch (err) {

View File

@ -1,7 +1,6 @@
import { cache, db as dbCore } from "@budibase/backend-core" import { cache, db as dbCore } from "@budibase/backend-core"
import { utils } from "@budibase/shared-core" import { utils } from "@budibase/shared-core"
import { import {
FieldType,
BBReferenceFieldSubType, BBReferenceFieldSubType,
DocumentType, DocumentType,
SEPARATOR, SEPARATOR,
@ -10,99 +9,93 @@ import { InvalidBBRefError } from "./errors"
const ROW_PREFIX = DocumentType.ROW + SEPARATOR const ROW_PREFIX = DocumentType.ROW + SEPARATOR
export function processInputBBReferences( export async function processInputBBReference(
value: string | { _id: string }, value: string | { _id: string },
type: FieldType.BB_REFERENCE_SINGLE subtype: BBReferenceFieldSubType.USER
): Promise<string | null> ): Promise<string | null> {
export function processInputBBReferences( if (value && Array.isArray(value)) {
value: string | string[] | { _id: string } | { _id: string }[], throw "BB_REFERENCE_SINGLE cannot be an array"
type: FieldType.BB_REFERENCE, }
subtype: BBReferenceFieldSubType let id = typeof value === "string" ? value : value?._id
): Promise<string | null>
export async function processInputBBReferences( if (!id) {
value: string | string[] | { _id: string } | { _id: string }[], return null
type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE, }
subtype?: BBReferenceFieldSubType
): Promise<string | string[] | null> {
switch (type) {
case FieldType.BB_REFERENCE: {
let referenceIds: string[] = []
if (Array.isArray(value)) { switch (subtype) {
referenceIds.push( case BBReferenceFieldSubType.USER: {
...value.map(idOrDoc => if (id.startsWith(ROW_PREFIX)) {
typeof idOrDoc === "string" ? idOrDoc : idOrDoc._id id = dbCore.getGlobalIDFromUserMetadataID(id)
)
)
} else if (typeof value !== "string") {
referenceIds.push(value._id)
} else {
referenceIds.push(
...value
.split(",")
.filter(x => x)
.map((id: string) => id.trim())
)
} }
// make sure all reference IDs are correct global user IDs try {
// they may be user metadata references (start with row prefix) await cache.user.getUser(id)
// and these need to be converted to global IDs return id
referenceIds = referenceIds.map(id => { } catch (e: any) {
if (id?.startsWith(ROW_PREFIX)) { if (e.statusCode === 404) {
return dbCore.getGlobalIDFromUserMetadataID(id) throw new InvalidBBRefError(id, BBReferenceFieldSubType.USER)
} else {
return id
} }
}) throw e
switch (subtype) {
case undefined:
throw "Subtype must be defined"
case BBReferenceFieldSubType.USER:
case BBReferenceFieldSubType.USERS: {
const { notFoundIds } = await cache.user.getUsers(referenceIds)
if (notFoundIds?.length) {
throw new InvalidBBRefError(
notFoundIds[0],
BBReferenceFieldSubType.USER
)
}
if (!referenceIds?.length) {
return null
}
if (subtype === BBReferenceFieldSubType.USERS) {
return referenceIds
}
return referenceIds.join(",")
}
default:
throw utils.unreachable(subtype)
} }
} }
case FieldType.BB_REFERENCE_SINGLE: {
if (value && Array.isArray(value)) {
throw "BB_REFERENCE_SINGLE cannot be an array"
}
const id = typeof value === "string" ? value : value._id
const user = await cache.user.getUser(id)
if (!user) {
throw new InvalidBBRefError(id, BBReferenceFieldSubType.USER)
}
return user._id!
}
default: default:
throw utils.unreachable(type) throw utils.unreachable(subtype)
}
}
export async function processInputBBReferences(
value: string | string[] | { _id: string }[],
subtype: BBReferenceFieldSubType
): Promise<string[] | null> {
if (!value || !value[0]) {
return null
}
let referenceIds
if (typeof value === "string") {
referenceIds = value
.split(",")
.map(u => u.trim())
.filter(u => !!u)
} else {
referenceIds = value.map(idOrDoc =>
typeof idOrDoc === "string" ? idOrDoc : idOrDoc._id
)
}
// make sure all reference IDs are correct global user IDs
// they may be user metadata references (start with row prefix)
// and these need to be converted to global IDs
referenceIds = referenceIds.map(id => {
if (id?.startsWith(ROW_PREFIX)) {
return dbCore.getGlobalIDFromUserMetadataID(id)
} else {
return id
}
})
switch (subtype) {
case undefined:
throw "Subtype must be defined"
case BBReferenceFieldSubType.USER:
case BBReferenceFieldSubType.USERS: {
const { notFoundIds } = await cache.user.getUsers(referenceIds)
if (notFoundIds?.length) {
throw new InvalidBBRefError(
notFoundIds[0],
BBReferenceFieldSubType.USER
)
}
if (!referenceIds?.length) {
return null
}
return referenceIds
}
default:
throw utils.unreachable(subtype)
} }
} }
@ -110,67 +103,25 @@ interface UserReferenceInfo {
_id: string _id: string
primaryDisplay: string primaryDisplay: string
email: string email: string
firstName: string firstName?: string
lastName: string lastName?: string
} }
export function processOutputBBReferences( export async function processOutputBBReference(
value: string, value: string | null | undefined,
type: FieldType.BB_REFERENCE_SINGLE subtype: BBReferenceFieldSubType.USER
): Promise<UserReferenceInfo | undefined> ): Promise<UserReferenceInfo | undefined> {
export function processOutputBBReferences( if (!value) {
value: string, return undefined
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType
): Promise<UserReferenceInfo[] | undefined>
export async function processOutputBBReferences(
value: string | string[],
type: FieldType.BB_REFERENCE | FieldType.BB_REFERENCE_SINGLE,
subtype?: BBReferenceFieldSubType
) {
if (value === null || value === undefined) {
// Already processed or nothing to process
return value || undefined
} }
switch (type) { switch (subtype) {
case FieldType.BB_REFERENCE: { case BBReferenceFieldSubType.USER: {
const ids =
typeof value === "string" ? value.split(",").filter(id => !!id) : value
switch (subtype) {
case undefined:
throw "Subtype must be defined"
case BBReferenceFieldSubType.USER:
case BBReferenceFieldSubType.USERS: {
const { users } = await cache.user.getUsers(ids)
if (!users.length) {
return undefined
}
return users.map(u => ({
_id: u._id,
primaryDisplay: u.email,
email: u.email,
firstName: u.firstName,
lastName: u.lastName,
}))
}
default:
throw utils.unreachable(subtype)
}
}
case FieldType.BB_REFERENCE_SINGLE: {
if (!value) {
return undefined
}
let user let user
try { try {
user = await cache.user.getUser(value as string) user = await cache.user.getUser(value as string)
} catch (err: any) { } catch (err: any) {
if (err.code !== 404) { if (err.statusCode !== 404) {
throw err throw err
} }
} }
@ -179,15 +130,45 @@ export async function processOutputBBReferences(
} }
return { return {
_id: user._id, _id: user._id!,
primaryDisplay: user.email, primaryDisplay: user.email,
email: user.email, email: user.email,
firstName: user.firstName, firstName: user.firstName,
lastName: user.lastName, lastName: user.lastName,
} }
} }
default: default:
throw utils.unreachable(type) throw utils.unreachable(subtype)
}
}
export async function processOutputBBReferences(
value: string | null | undefined,
subtype: BBReferenceFieldSubType
): Promise<UserReferenceInfo[] | undefined> {
if (!value) {
return undefined
}
const ids =
typeof value === "string" ? value.split(",").filter(id => !!id) : value
switch (subtype) {
case BBReferenceFieldSubType.USER:
case BBReferenceFieldSubType.USERS: {
const { users } = await cache.user.getUsers(ids)
if (!users.length) {
return undefined
}
return users.map(u => ({
_id: u._id!,
primaryDisplay: u.email,
email: u.email,
firstName: u.firstName,
lastName: u.lastName,
}))
}
default:
throw utils.unreachable(subtype)
} }
} }

View File

@ -12,7 +12,9 @@ import {
} from "@budibase/types" } from "@budibase/types"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { import {
processInputBBReference,
processInputBBReferences, processInputBBReferences,
processOutputBBReference,
processOutputBBReferences, processOutputBBReferences,
} from "./bbReferenceProcessor" } from "./bbReferenceProcessor"
import { isExternalTableID } from "../../integrations/utils" import { isExternalTableID } from "../../integrations/utils"
@ -161,13 +163,9 @@ export async function inputProcessing(
delete clonedRow[key].url delete clonedRow[key].url
} }
} else if (field.type === FieldType.BB_REFERENCE && value) { } else if (field.type === FieldType.BB_REFERENCE && value) {
clonedRow[key] = await processInputBBReferences( clonedRow[key] = await processInputBBReferences(value, field.subtype)
value,
field.type,
field.subtype
)
} else if (field.type === FieldType.BB_REFERENCE_SINGLE && value) { } else if (field.type === FieldType.BB_REFERENCE_SINGLE && value) {
clonedRow[key] = await processInputBBReferences(value, field.type) clonedRow[key] = await processInputBBReference(value, field.subtype)
} }
} }
@ -253,7 +251,6 @@ export async function outputProcessing<T extends Row[] | Row>(
for (let row of enriched) { for (let row of enriched) {
row[property] = await processOutputBBReferences( row[property] = await processOutputBBReferences(
row[property], row[property],
column.type,
column.subtype column.subtype
) )
} }
@ -262,9 +259,9 @@ export async function outputProcessing<T extends Row[] | Row>(
column.type == FieldType.BB_REFERENCE_SINGLE column.type == FieldType.BB_REFERENCE_SINGLE
) { ) {
for (let row of enriched) { for (let row of enriched) {
row[property] = await processOutputBBReferences( row[property] = await processOutputBBReference(
row[property], row[property],
column.type column.subtype
) )
} }
} }

View File

@ -1,8 +1,10 @@
import _ from "lodash" import _ from "lodash"
import * as backendCore from "@budibase/backend-core" import * as backendCore from "@budibase/backend-core"
import { BBReferenceFieldSubType, FieldType, User } from "@budibase/types" import { BBReferenceFieldSubType, User } from "@budibase/types"
import { import {
processInputBBReference,
processInputBBReferences, processInputBBReferences,
processOutputBBReference,
processOutputBBReferences, processOutputBBReferences,
} from "../bbReferenceProcessor" } from "../bbReferenceProcessor"
import { import {
@ -22,6 +24,7 @@ jest.mock("@budibase/backend-core", (): typeof backendCore => {
...actual.cache, ...actual.cache,
user: { user: {
...actual.cache.user, ...actual.cache.user,
getUser: jest.fn(actual.cache.user.getUser),
getUsers: jest.fn(actual.cache.user.getUsers), getUsers: jest.fn(actual.cache.user.getUsers),
}, },
}, },
@ -31,6 +34,9 @@ jest.mock("@budibase/backend-core", (): typeof backendCore => {
const config = new DBTestConfiguration() const config = new DBTestConfiguration()
describe("bbReferenceProcessor", () => { describe("bbReferenceProcessor", () => {
const cacheGetUserSpy = backendCore.cache.user.getUser as jest.MockedFunction<
typeof backendCore.cache.user.getUser
>
const cacheGetUsersSpy = backendCore.cache.user const cacheGetUsersSpy = backendCore.cache.user
.getUsers as jest.MockedFunction<typeof backendCore.cache.user.getUsers> .getUsers as jest.MockedFunction<typeof backendCore.cache.user.getUsers>
@ -56,6 +62,64 @@ describe("bbReferenceProcessor", () => {
jest.clearAllMocks() jest.clearAllMocks()
}) })
describe("processInputBBReference", () => {
describe("subtype user", () => {
it("validate valid string id", async () => {
const user = _.sample(users)
const userId = user!._id!
const result = await config.doInTenant(() =>
processInputBBReference(userId, BBReferenceFieldSubType.USER)
)
expect(result).toEqual(userId)
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
})
it("throws an error given an invalid id", async () => {
const userId = generator.guid()
await expect(
config.doInTenant(() =>
processInputBBReference(userId, BBReferenceFieldSubType.USER)
)
).rejects.toThrow(
new InvalidBBRefError(userId, BBReferenceFieldSubType.USER)
)
})
it("validate valid user object", async () => {
const userId = _.sample(users)!._id!
const result = await config.doInTenant(() =>
processInputBBReference({ _id: userId }, BBReferenceFieldSubType.USER)
)
expect(result).toEqual(userId)
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
})
it("empty strings will return null", async () => {
const result = await config.doInTenant(() =>
processInputBBReference("", BBReferenceFieldSubType.USER)
)
expect(result).toEqual(null)
})
it("should convert user medata IDs to global IDs", async () => {
const userId = _.sample(users)!._id!
const userMetadataId = backendCore.db.generateUserMetadataID(userId)
const result = await config.doInTenant(() =>
processInputBBReference(userMetadataId, BBReferenceFieldSubType.USER)
)
expect(result).toBe(userId)
})
})
})
describe("processInputBBReferences", () => { describe("processInputBBReferences", () => {
describe("subtype user", () => { describe("subtype user", () => {
it("validate valid string id", async () => { it("validate valid string id", async () => {
@ -63,14 +127,10 @@ describe("bbReferenceProcessor", () => {
const userId = user!._id! const userId = user!._id!
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(userId, BBReferenceFieldSubType.USER)
userId,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual(userId) expect(result).toEqual([userId])
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1) expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId]) expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId])
}) })
@ -80,11 +140,7 @@ describe("bbReferenceProcessor", () => {
await expect( await expect(
config.doInTenant(() => config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(userId, BBReferenceFieldSubType.USER)
userId,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
).rejects.toThrow( ).rejects.toThrow(
new InvalidBBRefError(userId, BBReferenceFieldSubType.USER) new InvalidBBRefError(userId, BBReferenceFieldSubType.USER)
@ -98,14 +154,10 @@ describe("bbReferenceProcessor", () => {
const userIdCsv = userIds.join(" , ") const userIdCsv = userIds.join(" , ")
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(userIdCsv, BBReferenceFieldSubType.USER)
userIdCsv,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual(userIds.join(",")) expect(result).toEqual(userIds)
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1) expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith(userIds) expect(cacheGetUsersSpy).toHaveBeenCalledWith(userIds)
}) })
@ -122,45 +174,22 @@ describe("bbReferenceProcessor", () => {
await expect( await expect(
config.doInTenant(() => config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(userIdCsv, BBReferenceFieldSubType.USER)
userIdCsv,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
).rejects.toThrow( ).rejects.toThrow(
new InvalidBBRefError(wrongId, BBReferenceFieldSubType.USER) new InvalidBBRefError(wrongId, BBReferenceFieldSubType.USER)
) )
}) })
it("validate valid user object", async () => {
const userId = _.sample(users)!._id!
const result = await config.doInTenant(() =>
processInputBBReferences(
{ _id: userId },
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
)
expect(result).toEqual(userId)
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId])
})
it("validate valid user object array", async () => { it("validate valid user object array", async () => {
const userIds = _.sampleSize(users, 3).map(x => x._id!) const inputUsers = _.sampleSize(users, 3).map(u => ({ _id: u._id! }))
const userIds = inputUsers.map(u => u._id)
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(inputUsers, BBReferenceFieldSubType.USER)
userIds,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual(userIds.join(",")) expect(result).toEqual(userIds)
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1) expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith(userIds) expect(cacheGetUsersSpy).toHaveBeenCalledWith(userIds)
}) })
@ -169,7 +198,7 @@ describe("bbReferenceProcessor", () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(
"", "",
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER BBReferenceFieldSubType.USER
) )
) )
@ -179,11 +208,7 @@ describe("bbReferenceProcessor", () => {
it("empty arrays will return null", async () => { it("empty arrays will return null", async () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences([], BBReferenceFieldSubType.USER)
[],
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual(null) expect(result).toEqual(null)
@ -193,13 +218,44 @@ describe("bbReferenceProcessor", () => {
const userId = _.sample(users)!._id! const userId = _.sample(users)!._id!
const userMetadataId = backendCore.db.generateUserMetadataID(userId) const userMetadataId = backendCore.db.generateUserMetadataID(userId)
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processInputBBReferences( processInputBBReferences(userMetadataId, BBReferenceFieldSubType.USER)
userMetadataId,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toBe(userId) expect(result).toEqual([userId])
})
})
})
describe("processOutputBBReference", () => {
describe("subtype user", () => {
it("fetches user given a valid string id", async () => {
const user = _.sample(users)!
const userId = user._id!
const result = await config.doInTenant(() =>
processOutputBBReference(userId, BBReferenceFieldSubType.USER)
)
expect(result).toEqual({
_id: user._id,
primaryDisplay: user.email,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
})
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
})
it("returns undefined given an unexisting user", async () => {
const userId = generator.guid()
const result = await config.doInTenant(() =>
processOutputBBReference(userId, BBReferenceFieldSubType.USER)
)
expect(result).toBeUndefined()
expect(cacheGetUserSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUserSpy).toHaveBeenCalledWith(userId)
}) })
}) })
}) })
@ -211,11 +267,7 @@ describe("bbReferenceProcessor", () => {
const userId = user._id! const userId = user._id!
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processOutputBBReferences( processOutputBBReferences(userId, BBReferenceFieldSubType.USER)
userId,
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER
)
) )
expect(result).toEqual([ expect(result).toEqual([
@ -239,7 +291,6 @@ describe("bbReferenceProcessor", () => {
const result = await config.doInTenant(() => const result = await config.doInTenant(() =>
processOutputBBReferences( processOutputBBReferences(
[userId1, userId2].join(","), [userId1, userId2].join(","),
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER BBReferenceFieldSubType.USER
) )
) )
@ -259,6 +310,46 @@ describe("bbReferenceProcessor", () => {
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1) expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId1, userId2]) expect(cacheGetUsersSpy).toHaveBeenCalledWith([userId1, userId2])
}) })
it("trims unexisting users user given a valid string id csv", async () => {
const [user1, user2] = _.sampleSize(users, 2)
const userId1 = user1._id!
const userId2 = user2._id!
const unexistingUserId1 = generator.guid()
const unexistingUserId2 = generator.guid()
const input = [
unexistingUserId1,
userId1,
unexistingUserId2,
userId2,
].join(",")
const result = await config.doInTenant(() =>
processOutputBBReferences(input, BBReferenceFieldSubType.USER)
)
expect(result).toHaveLength(2)
expect(result).toEqual(
expect.arrayContaining(
[user1, user2].map(u => ({
_id: u._id,
primaryDisplay: u.email,
email: u.email,
firstName: u.firstName,
lastName: u.lastName,
}))
)
)
expect(cacheGetUsersSpy).toHaveBeenCalledTimes(1)
expect(cacheGetUsersSpy).toHaveBeenCalledWith([
unexistingUserId1,
userId1,
unexistingUserId2,
userId2,
])
})
}) })
}) })
}) })

View File

@ -10,7 +10,9 @@ import {
import * as bbReferenceProcessor from "../bbReferenceProcessor" import * as bbReferenceProcessor from "../bbReferenceProcessor"
jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({ jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({
processInputBBReference: jest.fn(),
processInputBBReferences: jest.fn(), processInputBBReferences: jest.fn(),
processOutputBBReference: jest.fn(),
processOutputBBReferences: jest.fn(), processOutputBBReferences: jest.fn(),
})) }))
@ -19,7 +21,64 @@ describe("rowProcessor - inputProcessing", () => {
jest.resetAllMocks() jest.resetAllMocks()
}) })
it("processes BB references if on the schema and it's populated", async () => { const processInputBBReferenceMock =
bbReferenceProcessor.processInputBBReference as jest.Mock
const processInputBBReferencesMock =
bbReferenceProcessor.processInputBBReferences as jest.Mock
it("processes single BB references if on the schema and it's populated", async () => {
const userId = generator.guid()
const table: Table = {
_id: generator.guid(),
name: "TestTable",
type: "table",
sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL,
schema: {
name: {
type: FieldType.STRING,
name: "name",
constraints: {
presence: true,
type: "string",
},
},
user: {
type: FieldType.BB_REFERENCE_SINGLE,
subtype: BBReferenceFieldSubType.USER,
name: "user",
constraints: {
presence: true,
type: "string",
},
},
},
}
const newRow = {
name: "Jack",
user: "123",
}
const user = structures.users.user()
processInputBBReferenceMock.mockResolvedValue(user)
const { row } = await inputProcessing(userId, table, newRow)
expect(bbReferenceProcessor.processInputBBReference).toHaveBeenCalledTimes(
1
)
expect(bbReferenceProcessor.processInputBBReference).toHaveBeenCalledWith(
"123",
"user"
)
expect(row).toEqual({ ...newRow, user })
})
it("processes multiple BB references if on the schema and it's populated", async () => {
const userId = generator.guid() const userId = generator.guid()
const table: Table = { const table: Table = {
@ -56,9 +115,7 @@ describe("rowProcessor - inputProcessing", () => {
const user = structures.users.user() const user = structures.users.user()
;( processInputBBReferencesMock.mockResolvedValue(user)
bbReferenceProcessor.processInputBBReferences as jest.Mock
).mockResolvedValue(user)
const { row } = await inputProcessing(userId, table, newRow) const { row } = await inputProcessing(userId, table, newRow)
@ -67,7 +124,6 @@ describe("rowProcessor - inputProcessing", () => {
) )
expect(bbReferenceProcessor.processInputBBReferences).toHaveBeenCalledWith( expect(bbReferenceProcessor.processInputBBReferences).toHaveBeenCalledWith(
"123", "123",
"bb_reference",
"user" "user"
) )

View File

@ -11,7 +11,9 @@ import { generator, structures } from "@budibase/backend-core/tests"
import * as bbReferenceProcessor from "../bbReferenceProcessor" import * as bbReferenceProcessor from "../bbReferenceProcessor"
jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({ jest.mock("../bbReferenceProcessor", (): typeof bbReferenceProcessor => ({
processInputBBReference: jest.fn(),
processInputBBReferences: jest.fn(), processInputBBReferences: jest.fn(),
processOutputBBReference: jest.fn(),
processOutputBBReferences: jest.fn(), processOutputBBReferences: jest.fn(),
})) }))
@ -20,10 +22,12 @@ describe("rowProcessor - outputProcessing", () => {
jest.resetAllMocks() jest.resetAllMocks()
}) })
const processOutputBBReferenceMock =
bbReferenceProcessor.processOutputBBReference as jest.Mock
const processOutputBBReferencesMock = const processOutputBBReferencesMock =
bbReferenceProcessor.processOutputBBReferences as jest.Mock bbReferenceProcessor.processOutputBBReferences as jest.Mock
it("fetches bb user references given a populated field", async () => { it("fetches single user references given a populated field", async () => {
const table: Table = { const table: Table = {
_id: generator.guid(), _id: generator.guid(),
name: "TestTable", name: "TestTable",
@ -40,7 +44,7 @@ describe("rowProcessor - outputProcessing", () => {
}, },
}, },
user: { user: {
type: FieldType.BB_REFERENCE, type: FieldType.BB_REFERENCE_SINGLE,
subtype: BBReferenceFieldSubType.USER, subtype: BBReferenceFieldSubType.USER,
name: "user", name: "user",
constraints: { constraints: {
@ -57,18 +61,66 @@ describe("rowProcessor - outputProcessing", () => {
} }
const user = structures.users.user() const user = structures.users.user()
processOutputBBReferencesMock.mockResolvedValue(user) processOutputBBReferenceMock.mockResolvedValue(user)
const result = await outputProcessing(table, row, { squash: false }) const result = await outputProcessing(table, row, { squash: false })
expect(result).toEqual({ name: "Jack", user }) expect(result).toEqual({ name: "Jack", user })
expect(bbReferenceProcessor.processOutputBBReference).toHaveBeenCalledTimes(
1
)
expect(bbReferenceProcessor.processOutputBBReference).toHaveBeenCalledWith(
"123",
BBReferenceFieldSubType.USER
)
})
it("fetches users references given a populated field", async () => {
const table: Table = {
_id: generator.guid(),
name: "TestTable",
type: "table",
sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL,
schema: {
name: {
type: FieldType.STRING,
name: "name",
constraints: {
presence: true,
type: "string",
},
},
users: {
type: FieldType.BB_REFERENCE,
subtype: BBReferenceFieldSubType.USER,
name: "users",
constraints: {
presence: false,
type: "string",
},
},
},
}
const row = {
name: "Jack",
users: "123",
}
const users = [structures.users.user()]
processOutputBBReferencesMock.mockResolvedValue(users)
const result = await outputProcessing(table, row, { squash: false })
expect(result).toEqual({ name: "Jack", users })
expect( expect(
bbReferenceProcessor.processOutputBBReferences bbReferenceProcessor.processOutputBBReferences
).toHaveBeenCalledTimes(1) ).toHaveBeenCalledTimes(1)
expect(bbReferenceProcessor.processOutputBBReferences).toHaveBeenCalledWith( expect(bbReferenceProcessor.processOutputBBReferences).toHaveBeenCalledWith(
"123", "123",
FieldType.BB_REFERENCE,
BBReferenceFieldSubType.USER BBReferenceFieldSubType.USER
) )
}) })