diff --git a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts index f31462c68a..b2523a8548 100644 --- a/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts +++ b/packages/server/src/utilities/rowProcessor/bbReferenceProcessor.ts @@ -19,19 +19,12 @@ export async function processInputBBReferences( result.push(...value.split(",").map((id: string) => id.trim())) } - for (const id of result) { - try { - const user = await cache.user.getUser(id) - if (!user) { - throw new InvalidBBRefError(id, FieldSubtype.USER) - } - } catch (err: any) { - if (err != null && err.status === 404 && err.error === "not_found") { - throw new InvalidBBRefError(id, FieldSubtype.USER) - } - throw err - } + const { notFoundIds } = await cache.user.getUsers(result) + + if (notFoundIds?.length) { + throw new InvalidBBRefError(notFoundIds[0], FieldSubtype.USER) } + break default: throw utils.unreachable(subtype) diff --git a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts index 9166caa64d..3955157bec 100644 --- a/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts +++ b/packages/server/src/utilities/rowProcessor/tests/bbReferenceProcessor.spec.ts @@ -1,133 +1,140 @@ +import _ from "lodash" import * as backendCore from "@budibase/backend-core" import { FieldSubtype, User } from "@budibase/types" import { processInputBBReferences, processOutputBBReferences, } from "../bbReferenceProcessor" -import { generator, structures } from "@budibase/backend-core/tests" +import { + DBTestConfiguration, + generator, + structures, +} from "@budibase/backend-core/tests" import { InvalidBBRefError } from "../errors" jest.mock("@budibase/backend-core", (): typeof backendCore => { - const actual = jest.requireActual("@budibase/backend-core") + const actual: typeof backendCore = jest.requireActual( + "@budibase/backend-core" + ) return { ...actual, cache: { ...actual.cache, user: { - getUser: jest.fn(), - invalidateUser: jest.fn(), + ...actual.cache.user, + getUsers: jest.fn(actual.cache.user.getUsers), }, }, } }) +const config = new DBTestConfiguration() + describe("bbReferenceProcessor", () => { - const mockedCacheGetUser = backendCore.cache.user.getUser as jest.Mock + const cacheGetUsersSpy = backendCore.cache.user + .getUsers as jest.MockedFunction + + const users: User[] = [] + beforeAll(async () => { + const userCount = 10 + const userIds = generator.arrayOf(() => generator.guid(), { + min: userCount, + max: userCount, + }) + + await config.doInTenant(async () => { + const db = backendCore.context.getGlobalDB() + for (const userId of userIds) { + const user = structures.users.user({ _id: userId }) + await db.put(user) + users.push(user) + } + }) + }) beforeEach(() => { - jest.resetAllMocks() + jest.clearAllMocks() }) describe("processInputBBReferences", () => { describe("subtype user", () => { it("validate valid string id", async () => { - const userId = generator.guid() + const user = _.sample(users) + const userId = user!._id! - const userFromCache = structures.users.user() - mockedCacheGetUser.mockResolvedValueOnce(userFromCache) - - const result = await processInputBBReferences(userId, FieldSubtype.USER) + const result = await config.doInTenant(() => + processInputBBReferences(userId, FieldSubtype.USER) + ) expect(result).toEqual(userId) - expect(mockedCacheGetUser).toBeCalledTimes(1) - expect(mockedCacheGetUser).toBeCalledWith(userId) + expect(cacheGetUsersSpy).toBeCalledTimes(1) + expect(cacheGetUsersSpy).toBeCalledWith([userId]) }) it("throws an error given an invalid id", async () => { const userId = generator.guid() - mockedCacheGetUser.mockRejectedValue({ - status: 404, - error: "not_found", - }) - await expect( - processInputBBReferences(userId, FieldSubtype.USER) + config.doInTenant(() => + processInputBBReferences(userId, FieldSubtype.USER) + ) ).rejects.toThrowError(new InvalidBBRefError(userId, FieldSubtype.USER)) + expect(cacheGetUsersSpy).toBeCalledTimes(1) + expect(cacheGetUsersSpy).toBeCalledWith([userId]) }) it("validates valid user ids as csv", async () => { - const userIds: string[] = [] - for (let i = 0; i < 5; i++) { - const userId = generator.guid() - const user = structures.users.user({ _id: userId }) - mockedCacheGetUser.mockResolvedValueOnce(user) - userIds.push(userId) - } + const userIds = _.sampleSize(users, 5).map(x => x._id!) const userIdCsv = userIds.join(" , ") - const result = await processInputBBReferences( - userIdCsv, - FieldSubtype.USER + const result = await config.doInTenant(() => + processInputBBReferences(userIdCsv, FieldSubtype.USER) ) expect(result).toEqual(userIds.join(",")) - expect(mockedCacheGetUser).toBeCalledTimes(5) - userIds.forEach(userId => { - expect(mockedCacheGetUser).toBeCalledWith(userId) - }) + expect(cacheGetUsersSpy).toBeCalledTimes(1) + expect(cacheGetUsersSpy).toBeCalledWith(userIds) }) it("throws an error given an invalid id in a csv", async () => { - const userId1 = generator.guid() - const userId2 = generator.guid() - const userId3 = generator.guid() - mockedCacheGetUser.mockResolvedValueOnce( - structures.users.user({ _id: userId1 }) - ) - mockedCacheGetUser.mockResolvedValueOnce( - structures.users.user({ _id: userId2 }) - ) + const expectedUsers = _.sampleSize(users, 2).map(x => x._id!) + const wrongId = generator.guid() - const userIdCsv = [userId1, userId2, userId3].join(" , ") + const userIdCsv = [expectedUsers[0], wrongId, expectedUsers[1]].join( + " , " + ) await expect( - processInputBBReferences(userIdCsv, FieldSubtype.USER) + config.doInTenant(() => + processInputBBReferences(userIdCsv, FieldSubtype.USER) + ) ).rejects.toThrowError( - new InvalidBBRefError(userId3, FieldSubtype.USER) + new InvalidBBRefError(wrongId, FieldSubtype.USER) ) }) it("validate valid user object", async () => { - const userId = generator.guid() + const userId = _.sample(users)!._id! - const userFromCache = structures.users.user() - mockedCacheGetUser.mockResolvedValueOnce(userFromCache) - - const result = await processInputBBReferences( - { _id: userId }, - FieldSubtype.USER + const result = await config.doInTenant(() => + processInputBBReferences({ _id: userId }, FieldSubtype.USER) ) expect(result).toEqual(userId) - expect(mockedCacheGetUser).toBeCalledTimes(1) - expect(mockedCacheGetUser).toBeCalledWith(userId) + expect(cacheGetUsersSpy).toBeCalledTimes(1) + expect(cacheGetUsersSpy).toBeCalledWith([userId]) }) it("validate valid user object array", async () => { - const users = Array.from({ length: 3 }, () => ({ - _id: generator.guid(), - })) + const userIds = _.sampleSize(users, 3).map(x => x._id!) - mockedCacheGetUser.mockResolvedValue(structures.users.user()) + const result = await config.doInTenant(() => + processInputBBReferences(userIds, FieldSubtype.USER) + ) - const result = await processInputBBReferences(users, FieldSubtype.USER) - - expect(result).toEqual(users.map(x => x._id).join(",")) - expect(mockedCacheGetUser).toBeCalledTimes(3) - for (const user of users) { - expect(mockedCacheGetUser).toBeCalledWith(user._id) - } + expect(result).toEqual(userIds.join(",")) + expect(cacheGetUsersSpy).toBeCalledTimes(1) + expect(cacheGetUsersSpy).toBeCalledWith(userIds) }) }) })