From c20668ab1cbd764c4fa8fe068104c437d0742628 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 08:51:43 +0000 Subject: [PATCH 1/4] Bump follow-redirects from 1.15.2 to 1.15.6 in /qa-core Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- qa-core/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qa-core/yarn.lock b/qa-core/yarn.lock index 9b7a9c6c05..0eba6dd4d7 100644 --- a/qa-core/yarn.lock +++ b/qa-core/yarn.lock @@ -2062,9 +2062,9 @@ find-up@^4.0.0, find-up@^4.1.0: path-exists "^4.0.0" follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== forever-agent@~0.6.1: version "0.6.1" From fec6c7d2679f9776961ff7c1300b0acc6dcccfaf Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 15 Mar 2024 15:57:27 +0000 Subject: [PATCH 2/4] Fold some mocky unit tests into row.spec.ts --- .../server/src/api/routes/tests/row.spec.ts | 36 +++++ .../src/sdk/app/rows/search/internal.ts | 13 +- .../server/src/sdk/tests/rows/row.spec.ts | 132 ------------------ .../server/src/tests/utilities/api/base.ts | 25 ++-- .../src/tests/utilities/api/legacyView.ts | 2 +- .../server/src/tests/utilities/api/row.ts | 3 +- 6 files changed, 66 insertions(+), 145 deletions(-) delete mode 100644 packages/server/src/sdk/tests/rows/row.spec.ts diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index ee9af34965..854410dcf6 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -852,6 +852,42 @@ describe.each([ expect(Object.keys(row).length).toEqual(1) expect(row._id).toEqual(existing._id) }) + + it("should handle single quotes in row filtering", async () => { + const existing = await config.api.row.save(table._id!, {}) + const res = await config.api.row.exportRows(table._id!, { + rows: [`['${existing._id!}']`], + }) + const results = JSON.parse(res) + expect(results.length).toEqual(1) + const row = results[0] + expect(row._id).toEqual(existing._id) + }) + + it("should return an error on composite keys", async () => { + const existing = await config.api.row.save(table._id!, {}) + await config.api.row.exportRows( + table._id!, + { + rows: [`['${existing._id!}']`, "['d001', '10111']"], + }, + { + status: 400, + body: { + message: "Export data does not support composite keys.", + }, + } + ) + }) + + it("should return an error if no table is found", async () => { + const existing = await config.api.row.save(table._id!, {}) + await config.api.row.exportRows( + "1234567", + { rows: [existing._id!] }, + { status: 404 } + ) + }) }) describe("view 2.0", () => { diff --git a/packages/server/src/sdk/app/rows/search/internal.ts b/packages/server/src/sdk/app/rows/search/internal.ts index 2d3c32e02e..8147ca46ad 100644 --- a/packages/server/src/sdk/app/rows/search/internal.ts +++ b/packages/server/src/sdk/app/rows/search/internal.ts @@ -1,6 +1,7 @@ import { context, db, + HTTPError, SearchParams as InternalSearchParams, } from "@budibase/backend-core" import env from "../../../../environment" @@ -31,6 +32,7 @@ import sdk from "../../../../sdk" import { ExportRowsParams, ExportRowsResult } from "../search" import { searchInputMapping } from "./utils" import pick from "lodash/pick" +import { breakRowIdField } from "../../../../integrations/utils" export async function search(options: SearchParams) { const { tableId } = options @@ -103,7 +105,16 @@ export async function exportRows( let response = ( await db.allDocs({ include_docs: true, - keys: rowIds, + keys: rowIds.map((row: string) => { + const ids = breakRowIdField(row) + if (ids.length > 1) { + throw new HTTPError( + "Export data does not support composite keys.", + 400 + ) + } + return ids[0] + }), }) ).rows.map(row => row.doc) diff --git a/packages/server/src/sdk/tests/rows/row.spec.ts b/packages/server/src/sdk/tests/rows/row.spec.ts deleted file mode 100644 index 8b01356e35..0000000000 --- a/packages/server/src/sdk/tests/rows/row.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { exportRows } from "../../app/rows/search/external" -import sdk from "../.." -import { ExternalRequest } from "../../../api/controllers/row/ExternalRequest" -import { ExportRowsParams } from "../../app/rows/search" -import { Format } from "../../../api/controllers/view/exporters" -import { HTTPError } from "@budibase/backend-core" -import { Operation } from "@budibase/types" - -const mockDatasourcesGet = jest.fn() -const mockTableGet = jest.fn() -sdk.datasources.get = mockDatasourcesGet -sdk.tables.getTable = mockTableGet - -jest.mock("../../../api/controllers/row/ExternalRequest") -jest.mock("../../../utilities/rowProcessor", () => ({ - outputProcessing: jest.fn((_, rows) => rows), -})) - -jest.mock("../../../api/controllers/view/exporters", () => ({ - ...jest.requireActual("../../../api/controllers/view/exporters"), - Format: { - CSV: "csv", - }, -})) -jest.mock("../../../utilities/fileSystem") - -describe("external row sdk", () => { - describe("exportRows", () => { - function getExportOptions(): ExportRowsParams { - return { - tableId: "datasource__tablename", - format: Format.CSV, - query: {}, - } - } - - const externalRequestCall = jest.fn() - beforeAll(() => { - jest - .spyOn(ExternalRequest.prototype, "run") - .mockImplementation(externalRequestCall.mockResolvedValue([])) - }) - - afterEach(() => { - jest.clearAllMocks() - }) - - it("should throw a 400 if no datasource entities are present", async () => { - const exportOptions = getExportOptions() - await expect(exportRows(exportOptions)).rejects.toThrowError( - new HTTPError("Datasource has not been configured for plus API.", 400) - ) - }) - - it("should handle single quotes from a row ID", async () => { - mockDatasourcesGet.mockImplementation(async () => ({ - entities: { - tablename: { - schema: {}, - }, - }, - })) - const exportOptions = getExportOptions() - exportOptions.rowIds = ["['d001']"] - - await exportRows(exportOptions) - - expect(ExternalRequest).toBeCalledTimes(1) - expect(ExternalRequest).toBeCalledWith( - Operation.READ, - exportOptions.tableId, - undefined - ) - - expect(externalRequestCall).toBeCalledTimes(1) - expect(externalRequestCall).toBeCalledWith( - expect.objectContaining({ - filters: { - oneOf: { - _id: ["d001"], - }, - }, - }) - ) - }) - - it("should throw a 400 if any composite keys are present", async () => { - const exportOptions = getExportOptions() - exportOptions.rowIds = ["[123]", "['d001'%2C'10111']"] - await expect(exportRows(exportOptions)).rejects.toThrowError( - new HTTPError("Export data does not support composite keys.", 400) - ) - }) - - it("should throw a 400 if no table name was found", async () => { - const exportOptions = getExportOptions() - exportOptions.tableId = "datasource__" - exportOptions.rowIds = ["[123]"] - - await expect(exportRows(exportOptions)).rejects.toThrowError( - new HTTPError("Could not find table name.", 400) - ) - }) - - it("should only export specified columns", async () => { - mockDatasourcesGet.mockImplementation(async () => ({ - entities: { - tablename: { - schema: { - name: {}, - age: {}, - dob: {}, - }, - }, - }, - })) - const headers = ["name", "dob"] - - const result = await exportRows({ - tableId: "datasource__tablename", - format: Format.CSV, - query: {}, - columns: headers, - }) - - expect(result).toEqual({ - fileName: "export.csv", - content: `"name","dob"`, - }) - }) - }) -}) diff --git a/packages/server/src/tests/utilities/api/base.ts b/packages/server/src/tests/utilities/api/base.ts index 3f534fba86..4df58ff425 100644 --- a/packages/server/src/tests/utilities/api/base.ts +++ b/packages/server/src/tests/utilities/api/base.ts @@ -143,16 +143,12 @@ export abstract class TestAPI { return await request } - protected _request = async ( - method: Method, - url: string, - opts?: RequestOpts - ): Promise => { - const { expectations } = opts || {} + protected _checkResponse = ( + response: Response, + expectations?: Expectations + ) => { const { status = 200 } = expectations || {} - const response = await this._requestRaw(method, url, opts) - if (response.status !== status) { let message = `Expected status ${status} but got ${response.status}` @@ -191,6 +187,17 @@ export abstract class TestAPI { expect(response.body).toMatchObject(expectations.body) } - return response.body + return response + } + + protected _request = async ( + method: Method, + url: string, + opts?: RequestOpts + ): Promise => { + return this._checkResponse( + await this._requestRaw(method, url, opts), + opts?.expectations + ).body } } diff --git a/packages/server/src/tests/utilities/api/legacyView.ts b/packages/server/src/tests/utilities/api/legacyView.ts index b018988670..ae250a81e2 100644 --- a/packages/server/src/tests/utilities/api/legacyView.ts +++ b/packages/server/src/tests/utilities/api/legacyView.ts @@ -31,6 +31,6 @@ export class LegacyViewAPI extends TestAPI { query: { view: viewName, format }, expectations, }) - return response.text + return this._checkResponse(response, expectations).text } } diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index 86664574cb..052e66b86e 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -8,10 +8,8 @@ import { BulkImportResponse, SearchRowResponse, SearchParams, - DeleteRowRequest, DeleteRows, DeleteRow, - ExportRowsResponse, } from "@budibase/types" import { Expectations, TestAPI } from "./base" @@ -117,6 +115,7 @@ export class RowAPI extends TestAPI { expectations, } ) + this._checkResponse(response, expectations) return response.text } From 236b18863016a35d19a5e47c99a8c45c88ca548a Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Mon, 18 Mar 2024 17:43:30 +0100 Subject: [PATCH 3/4] Add missing devDependency --- packages/string-templates/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index f931cb60d7..4a26b263a9 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -44,6 +44,7 @@ "rollup-plugin-node-globals": "^1.4.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-terser": "^7.0.2", + "ts-jest": "29.1.1", "typescript": "5.2.2" } } From 872efcf944b8d3a56fa53ff1ae4ed818e28306ce Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 19 Mar 2024 09:02:14 +0000 Subject: [PATCH 4/4] Add missing store subscription to fix hard crash if automation error exists --- packages/builder/src/pages/builder/portal/apps/index.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index adea7600a6..9a073d041f 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -85,7 +85,7 @@ } const automationErrorMessage = appId => { - const app = enrichedApps.find(app => app.devId === appId) + const app = $enrichedApps.find(app => app.devId === appId) const errors = automationErrors[appId] return `${app.name} - Automation error (${errorCount(errors)})` }