diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.ts similarity index 66% rename from packages/server/src/api/routes/tests/misc.spec.js rename to packages/server/src/api/routes/tests/misc.spec.ts index fe7da1a119..425c7212d0 100644 --- a/packages/server/src/api/routes/tests/misc.spec.js +++ b/packages/server/src/api/routes/tests/misc.spec.ts @@ -1,11 +1,13 @@ -const setup = require("./utilities") -const tableUtils = require("../../controllers/table/utils") +import { handleDataImport } from "../../controllers/table/utils" +import TestConfiguration from "../../../tests/utilities/TestConfiguration" +import { AutoFieldSubType, FieldType, JsonFieldSubType } from "@budibase/types" describe("run misc tests", () => { - let request = setup.getRequest() - let config = setup.getConfig() + const config = new TestConfiguration() - afterAll(setup.afterAll) + afterAll(() => { + config.end() + }) beforeAll(async () => { await config.init() @@ -13,69 +15,67 @@ describe("run misc tests", () => { describe("/bbtel", () => { it("check if analytics enabled", async () => { - const res = await request - .get(`/api/bbtel`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(typeof res.body.enabled).toEqual("boolean") + const { enabled } = await config.api.misc.bbtel() + expect(enabled).toEqual(true) }) }) describe("/health", () => { it("should confirm healthy", async () => { - await request.get("/health").expect(200) + await config.api.misc.health() }) }) describe("/version", () => { it("should confirm version", async () => { - const res = await request.get("/version").expect(200) - const text = res.text - if (text.includes("alpha")) { - expect(text.split(".").length).toEqual(4) + const version = await config.api.misc.version() + if (version.includes("alpha")) { + expect(version.split(".").length).toEqual(4) } else { - expect(text.split(".").length).toEqual(3) + expect(version.split(".").length).toEqual(3) } }) }) describe("test table utilities", () => { it("should be able to import data", async () => { - return config.doInContext(null, async () => { + return config.doInContext("", async () => { const table = await config.createTable({ name: "table", type: "table", - key: "name", schema: { a: { - type: "string", + type: FieldType.STRING, + name: "a", constraints: { type: "string", }, }, b: { - type: "string", + name: "b", + type: FieldType.STRING, constraints: { type: "string", }, }, c: { - type: "string", + name: "c", + type: FieldType.STRING, constraints: { type: "string", }, }, d: { - type: "string", + name: "d", + type: FieldType.STRING, constraints: { type: "string", }, }, e: { name: "Auto ID", - type: "number", - subtype: "autoID", + type: FieldType.NUMBER, + subtype: AutoFieldSubType.AUTO_ID, icon: "ri-magic-line", autocolumn: true, constraints: { @@ -88,9 +88,9 @@ describe("run misc tests", () => { }, }, f: { - type: "array", + type: FieldType.ARRAY, constraints: { - type: "array", + type: JsonFieldSubType.ARRAY, presence: { allowEmpty: true, }, @@ -100,7 +100,7 @@ describe("run misc tests", () => { sortable: false, }, g: { - type: "options", + type: FieldType.OPTIONS, constraints: { type: "string", presence: false, @@ -118,16 +118,18 @@ describe("run misc tests", () => { { a: "13", b: "14", c: "15", d: "16", g: "Omega" }, ] // Shift specific row tests to the row spec - await tableUtils.handleDataImport(table, { - importRows, - user: { userId: "test" }, - }) + await handleDataImport(table, { importRows, userId: "test" }) // 4 rows imported, the auto ID starts at 1 // We expect the handleDataImport function to update the lastID + + // @ts-expect-error - fields have type FieldSchema, not specific + // subtypes. expect(table.schema.e.lastID).toEqual(4) // Array/Multi - should have added a new value to the inclusion. + // @ts-expect-error - fields have type FieldSchema, not specific + // subtypes. expect(table.schema.f.constraints.inclusion).toEqual([ "Four", "One", @@ -136,6 +138,8 @@ describe("run misc tests", () => { ]) // Options - should have a new value in the inclusion + // @ts-expect-error - fields have type FieldSchema, not specific + // subtypes. expect(table.schema.g.constraints.inclusion).toEqual([ "Alpha", "Beta", @@ -143,25 +147,25 @@ describe("run misc tests", () => { "Omega", ]) - const rows = await config.getRows() + const rows = await config.api.row.fetch(table._id!) expect(rows.length).toEqual(4) - const rowOne = rows.find(row => row.e === 1) + const rowOne = rows.find(row => row.e === 1)! expect(rowOne.a).toEqual("1") expect(rowOne.f).toEqual(["One"]) expect(rowOne.g).toEqual("Alpha") - const rowTwo = rows.find(row => row.e === 2) + const rowTwo = rows.find(row => row.e === 2)! expect(rowTwo.a).toEqual("5") expect(rowTwo.f).toEqual([]) expect(rowTwo.g).toEqual(undefined) - const rowThree = rows.find(row => row.e === 3) + const rowThree = rows.find(row => row.e === 3)! expect(rowThree.a).toEqual("9") expect(rowThree.f).toEqual(["Two", "Four"]) expect(rowThree.g).toEqual(undefined) - const rowFour = rows.find(row => row.e === 4) + const rowFour = rows.find(row => row.e === 4)! expect(rowFour.a).toEqual("13") expect(rowFour.f).toEqual(undefined) expect(rowFour.g).toEqual("Omega") diff --git a/packages/server/src/integrations/tests/TestConfiguration.js b/packages/server/src/integrations/tests/TestConfiguration.js deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/server/src/middleware/selfhost.ts b/packages/server/src/middleware/selfhost.ts deleted file mode 100644 index 3571931835..0000000000 --- a/packages/server/src/middleware/selfhost.ts +++ /dev/null @@ -1,12 +0,0 @@ -import env from "../environment" -import { Ctx } from "@budibase/types" - -// if added as a middleware will stop requests unless builder is in self host mode -// or cloud is in self host -export default async (ctx: Ctx, next: any) => { - if (env.SELF_HOSTED) { - await next() - return - } - ctx.throw(400, "Endpoint unavailable in cloud hosting.") -} diff --git a/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.js b/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.js deleted file mode 100644 index 5c500f8723..0000000000 --- a/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import ensureTenantAppOwnership from "../ensureTenantAppOwnership" -import { tenancy, utils } from "@budibase/backend-core" - -jest.mock("@budibase/backend-core", () => ({ - ...jest.requireActual("@budibase/backend-core"), - tenancy: { - getTenantId: jest.fn(), - }, - utils: { - getAppIdFromCtx: jest.fn(), - }, -})) - -class TestConfiguration { - constructor(appId = "tenant_1") { - this.next = jest.fn() - this.throw = jest.fn() - this.middleware = ensureTenantAppOwnership - - this.ctx = { - next: this.next, - throw: this.throw, - } - - utils.getAppIdFromCtx.mockResolvedValue(appId) - } - - async executeMiddleware() { - return this.middleware(this.ctx, this.next) - } - - afterEach() { - jest.clearAllMocks() - } -} - -describe("Ensure Tenant Ownership Middleware", () => { - let config - - beforeEach(() => { - config = new TestConfiguration() - }) - - afterEach(() => { - config.afterEach() - }) - - it("calls next() when appId matches tenant ID", async () => { - tenancy.getTenantId.mockReturnValue("tenant_1") - - await config.executeMiddleware() - - expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx) - expect(config.next).toHaveBeenCalled() - }) - - it("throws when tenant appId does not match tenant ID", async () => { - const appId = "app_dev_tenant3_fce449c4d75b4e4a9c7a6980d82a3e22" - utils.getAppIdFromCtx.mockResolvedValue(appId) - tenancy.getTenantId.mockReturnValue("tenant_2") - - await config.executeMiddleware() - - expect(utils.getAppIdFromCtx).toHaveBeenCalledWith(config.ctx) - expect(config.throw).toHaveBeenCalledWith(403, "Unauthorized") - }) - - it("throws 400 when appId is missing", async () => { - utils.getAppIdFromCtx.mockResolvedValue(null) - - await config.executeMiddleware() - - expect(config.throw).toHaveBeenCalledWith(400, "appId must be provided") - }) -}) diff --git a/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.ts b/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.ts new file mode 100644 index 0000000000..c832ff645c --- /dev/null +++ b/packages/server/src/middleware/tests/ensureTenantAppOwnership.spec.ts @@ -0,0 +1,52 @@ +import { UserCtx } from "@budibase/types" +import ensureTenantAppOwnership from "../ensureTenantAppOwnership" +import { context, Header, HTTPError } from "@budibase/backend-core" + +function ctx(opts?: { appId: string }) { + const ctx = { + throw: (status: number, message: string) => { + throw new HTTPError(message, status) + }, + path: "", + request: { + headers: {}, + }, + } as unknown as UserCtx + if (opts?.appId) { + ctx.request.headers[Header.APP_ID] = opts.appId + } + return ctx +} + +describe("Ensure Tenant Ownership Middleware", () => { + const tenantId = "tenant1" + const appId = `app_dev_${tenantId}_fce449c4d75b4e4a9c7a6980d82a3e22` + + it("calls next() when appId matches tenant ID", async () => { + await context.doInTenant(tenantId, async () => { + let called = false + await ensureTenantAppOwnership(ctx({ appId }), () => { + called = true + }) + expect(called).toBe(true) + }) + }) + + it("throws when tenant appId does not match tenant ID", async () => { + let called = false + await expect(async () => { + await context.doInTenant("tenant_2", async () => { + await ensureTenantAppOwnership(ctx({ appId }), () => { + called = true + }) + }) + }).rejects.toThrow("Unauthorized") + expect(called).toBe(false) + }) + + it("throws 400 when appId is missing", async () => { + await expect(ensureTenantAppOwnership(ctx(), () => {})).rejects.toThrow( + "appId must be provided" + ) + }) +}) diff --git a/packages/server/src/middleware/tests/resourceId.spec.js b/packages/server/src/middleware/tests/resourceId.spec.js deleted file mode 100644 index 759aa46f00..0000000000 --- a/packages/server/src/middleware/tests/resourceId.spec.js +++ /dev/null @@ -1,105 +0,0 @@ -const { - paramResource, - paramSubResource, - bodyResource, - bodySubResource, - ResourceIdGetter, -} = require("../resourceId") - -class TestConfiguration { - constructor(middleware) { - this.middleware = middleware - this.ctx = { - request: {}, - } - this.next = jest.fn() - } - - setParams(params) { - this.ctx.params = params - } - - setBody(body) { - this.ctx.body = body - } - - executeMiddleware() { - return this.middleware(this.ctx, this.next) - } -} - -describe("resourceId middleware", () => { - it("calls next() when there is no request object to parse", () => { - const config = new TestConfiguration(paramResource("main")) - - config.executeMiddleware() - - expect(config.next).toHaveBeenCalled() - expect(config.ctx.resourceId).toBeUndefined() - }) - - it("generates a resourceId middleware for context query parameters", () => { - const config = new TestConfiguration(paramResource("main")) - config.setParams({ - main: "test", - }) - - config.executeMiddleware() - - expect(config.ctx.resourceId).toEqual("test") - }) - - it("generates a resourceId middleware for context query sub parameters", () => { - const config = new TestConfiguration(paramSubResource("main", "sub")) - config.setParams({ - main: "main", - sub: "test", - }) - - config.executeMiddleware() - - expect(config.ctx.resourceId).toEqual("main") - expect(config.ctx.subResourceId).toEqual("test") - }) - - it("generates a resourceId middleware for context request body", () => { - const config = new TestConfiguration(bodyResource("main")) - config.setBody({ - main: "test", - }) - - config.executeMiddleware() - - expect(config.ctx.resourceId).toEqual("test") - }) - - it("generates a resourceId middleware for context request body sub fields", () => { - const config = new TestConfiguration(bodySubResource("main", "sub")) - config.setBody({ - main: "main", - sub: "test", - }) - - config.executeMiddleware() - - expect(config.ctx.resourceId).toEqual("main") - expect(config.ctx.subResourceId).toEqual("test") - }) - - it("parses resourceIds correctly for custom middlewares", () => { - const middleware = new ResourceIdGetter("body") - .mainResource("custom") - .subResource("customSub") - .build() - let config = new TestConfiguration(middleware) - config.setBody({ - custom: "test", - customSub: "subtest", - }) - - config.executeMiddleware() - - expect(config.ctx.resourceId).toEqual("test") - expect(config.ctx.subResourceId).toEqual("subtest") - }) -}) diff --git a/packages/server/src/middleware/tests/resourceId.spec.ts b/packages/server/src/middleware/tests/resourceId.spec.ts new file mode 100644 index 0000000000..3ceb55af3c --- /dev/null +++ b/packages/server/src/middleware/tests/resourceId.spec.ts @@ -0,0 +1,93 @@ +import { Ctx } from "@budibase/types" +import { + paramResource, + paramSubResource, + bodyResource, + bodySubResource, + ResourceIdGetter, +} from "../resourceId" + +describe("resourceId middleware", () => { + it("calls next() when there is no request object to parse", () => { + const ctx = { request: {} } as Ctx + let called = false + paramResource("main")(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toBeUndefined() + }) + + it("generates a resourceId middleware for context query parameters", () => { + const ctx = { request: {}, params: { main: "test" } } as unknown as Ctx + let called = false + paramResource("main")(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toEqual("test") + }) + + it("generates a resourceId middleware for context query sub parameters", () => { + const ctx = { + request: {}, + params: { main: "main", sub: "test" }, + } as unknown as Ctx + let called = false + paramSubResource("main", "sub")(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toEqual("main") + expect(ctx.subResourceId).toEqual("test") + }) + + it("generates a resourceId middleware for context request body", () => { + const ctx = { request: {}, body: { main: "main" } } as unknown as Ctx + let called = false + bodyResource("main")(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toEqual("main") + }) + + it("generates a resourceId middleware for context request body sub fields", () => { + const ctx = { + request: {}, + body: { main: "main", sub: "test" }, + } as unknown as Ctx + let called = false + bodySubResource("main", "sub")(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toEqual("main") + expect(ctx.subResourceId).toEqual("test") + }) + + it("parses resourceIds correctly for custom middlewares", () => { + const middleware = new ResourceIdGetter("body") + .mainResource("custom") + .subResource("customSub") + .build() + + const ctx = { + request: {}, + body: { custom: "test", customSub: "subTest" }, + } as unknown as Ctx + let called = false + middleware(ctx, () => { + called = true + }) + + expect(called).toBe(true) + expect(ctx.resourceId).toEqual("test") + expect(ctx.subResourceId).toEqual("subTest") + }) +}) diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js deleted file mode 100644 index 03372e9783..0000000000 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -const selfHostMiddleware = require("../selfhost").default -const env = require("../../environment") -jest.mock("../../environment") - -class TestConfiguration { - constructor() { - this.next = jest.fn() - this.throw = jest.fn() - this.middleware = selfHostMiddleware - - this.ctx = { - next: this.next, - throw: this.throw, - } - } - - executeMiddleware() { - return this.middleware(this.ctx, this.next) - } - - afterEach() { - jest.clearAllMocks() - } -} - -describe("Self host middleware", () => { - let config - - beforeEach(() => { - config = new TestConfiguration() - }) - - afterEach(() => { - config.afterEach() - }) - - it("calls next() when SELF_HOSTED env var is set", async () => { - env.SELF_HOSTED = 1 - - await config.executeMiddleware() - expect(config.next).toHaveBeenCalled() - }) -}) diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts index 4c96f36b43..b1d1c904ae 100644 --- a/packages/server/src/tests/utilities/api/index.ts +++ b/packages/server/src/tests/utilities/api/index.ts @@ -19,6 +19,7 @@ import { PluginAPI } from "./plugin" import { WebhookAPI } from "./webhook" import { EnvironmentAPI } from "./environment" import { UserPublicAPI } from "./public/user" +import { MiscAPI } from "./misc" export default class API { application: ApplicationAPI @@ -28,6 +29,7 @@ export default class API { datasource: DatasourceAPI environment: EnvironmentAPI legacyView: LegacyViewAPI + misc: MiscAPI permission: PermissionAPI plugin: PluginAPI query: QueryAPI @@ -53,6 +55,7 @@ export default class API { this.datasource = new DatasourceAPI(config) this.environment = new EnvironmentAPI(config) this.legacyView = new LegacyViewAPI(config) + this.misc = new MiscAPI(config) this.permission = new PermissionAPI(config) this.plugin = new PluginAPI(config) this.query = new QueryAPI(config) diff --git a/packages/server/src/tests/utilities/api/misc.ts b/packages/server/src/tests/utilities/api/misc.ts new file mode 100644 index 0000000000..e46d11e29b --- /dev/null +++ b/packages/server/src/tests/utilities/api/misc.ts @@ -0,0 +1,18 @@ +import { AnalyticsEnabledResponse } from "@budibase/types" +import { Expectations, TestAPI } from "./base" + +export class MiscAPI extends TestAPI { + health = async (expectations?: Expectations) => { + return await this._get("/health", { expectations }) + } + + version = async (expectations?: Expectations) => { + return (await this._requestRaw("get", "/version", { expectations })).text + } + + bbtel = async (expectations?: Expectations) => { + return await this._get("/api/bbtel", { + expectations, + }) + } +}