diff --git a/packages/server/src/api/routes/public/tests/users.spec.ts b/packages/server/src/api/routes/public/tests/users.spec.ts index 4ca9ff8104..4983761c3a 100644 --- a/packages/server/src/api/routes/public/tests/users.spec.ts +++ b/packages/server/src/api/routes/public/tests/users.spec.ts @@ -1,27 +1,44 @@ import * as setup from "../../tests/utilities" -import { generateMakeRequest, MakeRequestResponse } from "./utils" import { User } from "@budibase/types" import { mocks } from "@budibase/backend-core/tests" +import nock from "nock" +import environment from "../../../../environment" +import TestConfiguration from "../../../../tests/utilities/TestConfiguration" -import * as workerRequests from "../../../../utilities/workerRequests" - -const mockedWorkerReq = jest.mocked(workerRequests) - -let config = setup.getConfig() -let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse +const config = new TestConfiguration() +let globalUser: User beforeAll(async () => { await config.init() - globalUser = await config.globalUser() - apiKey = await config.generateApiKey(globalUser._id) - makeRequest = generateMakeRequest(apiKey) - mockedWorkerReq.readGlobalUser.mockImplementation(() => - Promise.resolve(globalUser) - ) }) afterAll(setup.afterAll) +beforeEach(async () => { + globalUser = await config.globalUser() + + nock.cleanAll() + nock(environment.WORKER_URL!) + .get(`/api/global/users/${globalUser._id}`) + .reply(200, (uri, body) => { + return globalUser + }) + .persist() + + nock(environment.WORKER_URL!) + .post(`/api/global/users`) + .reply(200, (uri, body) => { + const updatedUser = body as User + if (updatedUser._id === globalUser._id) { + globalUser = updatedUser + return globalUser + } else { + throw new Error("User not found") + } + }) + .persist() +}) + function base() { return { tenantId: config.getTenantId(), @@ -30,37 +47,26 @@ function base() { } } -function updateMock() { - mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body) -} - -describe("check user endpoints", () => { +describe.only("check user endpoints", () => { it("should not allow a user to update their own roles", async () => { - const res = await makeRequest("put", `/users/${globalUser._id}`, { - ...globalUser, - roles: { - app_1: "ADMIN", - }, - }) - expect( - mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"] - ).toBeUndefined() - expect(res.status).toBe(200) - expect(res.body.data.roles["app_1"]).toBeUndefined() + await config.withUser(globalUser, () => + config.api.public.user.update({ + ...globalUser, + roles: { app_1: "ADMIN" }, + }) + ) + const updatedUser = await config.api.user.find(globalUser._id!) + expect(updatedUser.roles?.app_1).toBeUndefined() }) it("should not allow a user to delete themselves", async () => { - const res = await makeRequest("delete", `/users/${globalUser._id}`) - expect(res.status).toBe(405) - expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined() + await config.withUser(globalUser, () => + config.api.public.user.destroy(globalUser._id!, { status: 405 }) + ) }) }) describe("no user role update in free", () => { - beforeAll(() => { - updateMock() - }) - it("should not allow 'roles' to be updated", async () => { const res = await makeRequest("post", "/users", { ...base(), @@ -94,7 +100,6 @@ describe("no user role update in free", () => { describe("no user role update in business", () => { beforeAll(() => { - updateMock() mocks.licenses.useExpandedPublicApi() }) diff --git a/packages/server/src/api/routes/tests/utilities/index.ts b/packages/server/src/api/routes/tests/utilities/index.ts index dcb8ccd6c0..944a56d7ba 100644 --- a/packages/server/src/api/routes/tests/utilities/index.ts +++ b/packages/server/src/api/routes/tests/utilities/index.ts @@ -3,44 +3,6 @@ import supertest from "supertest" export * as structures from "../../../../tests/utilities/structures" -function user() { - return { - _id: "user", - _rev: "rev", - createdAt: Date.now(), - email: "test@example.com", - roles: {}, - tenantId: "default", - status: "active", - } -} - -jest.mock("../../../../utilities/workerRequests", () => ({ - getGlobalUsers: jest.fn(() => { - return { - _id: "us_uuid1", - } - }), - getGlobalSelf: jest.fn(() => { - return { - _id: "us_uuid1", - } - }), - allGlobalUsers: jest.fn(() => { - return [user()] - }), - readGlobalUser: jest.fn(() => { - return user() - }), - saveGlobalUser: jest.fn(() => { - return { _id: "user", _rev: "rev" } - }), - deleteGlobalUser: jest.fn(() => { - return { message: "deleted user" } - }), - removeAppFromUserRoles: jest.fn(), -})) - export function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)) } diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index edb397169d..219a428f05 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -67,6 +67,7 @@ import { View, Webhook, WithRequired, + DevInfo, } from "@budibase/types" import API from "./api" @@ -248,7 +249,7 @@ export default class TestConfiguration { } } - async withUser(user: User, f: () => Promise) { + async withUser(user: User, f: () => Promise): Promise { const oldUser = this.user this.user = user try { @@ -469,7 +470,10 @@ export default class TestConfiguration { } } - defaultHeaders(extras = {}, prodApp = false) { + defaultHeaders( + extras: Record = {}, + prodApp = false + ) { const tenantId = this.getTenantId() const user = this.getUser() const authObj: AuthToken = { @@ -498,10 +502,13 @@ export default class TestConfiguration { } } - publicHeaders({ prodApp = true } = {}) { + publicHeaders({ + prodApp = true, + extras = {}, + }: { prodApp?: boolean; extras?: Record } = {}) { const appId = prodApp ? this.prodAppId : this.appId - const headers: any = { + const headers: Record = { Accept: "application/json", Cookie: "", } @@ -514,6 +521,7 @@ export default class TestConfiguration { return { ...headers, ...this.temporaryHeaders, + ...extras, } } @@ -577,17 +585,17 @@ export default class TestConfiguration { } const db = tenancy.getTenantDB(this.getTenantId()) const id = dbCore.generateDevInfoID(userId) - let devInfo: any - try { - devInfo = await db.get(id) - } catch (err) { - devInfo = { _id: id, userId } + const devInfo = await db.tryGet(id) + if (devInfo && devInfo.apiKey) { + return devInfo.apiKey } - devInfo.apiKey = encryption.encrypt( + + const apiKey = encryption.encrypt( `${this.getTenantId()}${dbCore.SEPARATOR}${newid()}` ) - await db.put(devInfo) - return devInfo.apiKey + const newDevInfo: DevInfo = { _id: id, userId, apiKey } + await db.put(newDevInfo) + return apiKey } // APP diff --git a/packages/server/src/tests/utilities/api/base.ts b/packages/server/src/tests/utilities/api/base.ts index 39ac5cefc0..177c3c3c0b 100644 --- a/packages/server/src/tests/utilities/api/base.ts +++ b/packages/server/src/tests/utilities/api/base.ts @@ -46,6 +46,7 @@ export interface RequestOpts { export abstract class TestAPI { config: TestConfiguration request: SuperTest + prefix = "" constructor(config: TestConfiguration) { this.config = config @@ -53,26 +54,26 @@ export abstract class TestAPI { } protected _get = async (url: string, opts?: RequestOpts): Promise => { - return await this._request("get", url, opts) + return await this._request("get", `${this.prefix}${url}`, opts) } protected _post = async (url: string, opts?: RequestOpts): Promise => { - return await this._request("post", url, opts) + return await this._request("post", `${this.prefix}${url}`, opts) } protected _put = async (url: string, opts?: RequestOpts): Promise => { - return await this._request("put", url, opts) + return await this._request("put", `${this.prefix}${url}`, opts) } protected _patch = async (url: string, opts?: RequestOpts): Promise => { - return await this._request("patch", url, opts) + return await this._request("patch", `${this.prefix}${url}`, opts) } protected _delete = async ( url: string, opts?: RequestOpts ): Promise => { - return await this._request("delete", url, opts) + return await this._request("delete", `${this.prefix}${url}`, opts) } protected _requestRaw = async ( @@ -88,7 +89,6 @@ export abstract class TestAPI { fields = {}, files = {}, expectations, - publicUser = false, } = opts || {} const { status = 200 } = expectations || {} const expectHeaders = expectations?.headers || {} @@ -97,7 +97,7 @@ export abstract class TestAPI { expectHeaders["Content-Type"] = /^application\/json/ } - let queryParams = [] + let queryParams: string[] = [] for (const [key, value] of Object.entries(query)) { if (value) { queryParams.push(`${key}=${value}`) @@ -107,18 +107,10 @@ export abstract class TestAPI { url += `?${queryParams.join("&")}` } - const headersFn = publicUser - ? (_extras = {}) => - this.config.publicHeaders.bind(this.config)({ - prodApp: opts?.useProdApp, - }) - : (extras = {}) => - this.config.defaultHeaders.bind(this.config)(extras, opts?.useProdApp) - const app = getServer() let req = request(app)[method](url) req = req.set( - headersFn({ + await this.getHeaders(opts, { "x-budibase-include-stacktrace": "true", }) ) @@ -167,6 +159,17 @@ export abstract class TestAPI { } } + protected async getHeaders( + opts?: RequestOpts, + extras?: Record + ): Promise> { + if (opts?.publicUser) { + return this.config.publicHeaders({ prodApp: opts?.useProdApp, extras }) + } else { + return this.config.defaultHeaders(extras, opts?.useProdApp) + } + } + protected _checkResponse = ( response: Response, expectations?: Expectations @@ -236,3 +239,24 @@ export abstract class TestAPI { ).body } } + +export abstract class PublicAPI extends TestAPI { + prefix = "/api/public/v1" + + protected async getHeaders( + opts?: RequestOpts, + extras?: Record + ): Promise> { + const apiKey = await this.config.generateApiKey() + + const headers: Record = { + Accept: "application/json", + Host: this.config.tenantHost(), + "x-budibase-api-key": apiKey, + "x-budibase-app-id": this.config.getAppId(), + ...extras, + } + + return headers + } +} diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index 2fdf726b6c..1252b10e40 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -17,6 +17,7 @@ import { RowActionAPI } from "./rowAction" import { AutomationAPI } from "./automation" import { PluginAPI } from "./plugin" import { WebhookAPI } from "./webhook" +import { UserPublicAPI } from "./public/user" export default class API { application: ApplicationAPI @@ -38,6 +39,10 @@ export default class API { viewV2: ViewV2API webhook: WebhookAPI + public: { + user: UserPublicAPI + } + constructor(config: TestConfiguration) { this.application = new ApplicationAPI(config) this.attachment = new AttachmentAPI(config) @@ -57,5 +62,8 @@ export default class API { this.user = new UserAPI(config) this.viewV2 = new ViewV2API(config) this.webhook = new WebhookAPI(config) + this.public = { + user: new UserPublicAPI(config), + } } } diff --git a/packages/server/src/tests/utilities/api/public/user.ts b/packages/server/src/tests/utilities/api/public/user.ts new file mode 100644 index 0000000000..e33b85ad9a --- /dev/null +++ b/packages/server/src/tests/utilities/api/public/user.ts @@ -0,0 +1,19 @@ +import { User } from "@budibase/types" +import { Expectations, PublicAPI } from "../base" + +export class UserPublicAPI extends PublicAPI { + find = async (id: string, expectations?: Expectations): Promise => { + return await this._get(`/users/${id}`, { expectations }) + } + + update = async (user: User, expectations?: Expectations): Promise => { + return await this._put(`/users/${user._id}`, { + body: user, + expectations, + }) + } + + destroy = async (id: string, expectations?: Expectations): Promise => { + return await this._delete(`/users/${id}`, { expectations }) + } +}