1
0
Fork 0
mirror of synced 2024-05-17 10:53:15 +12:00

Merge branch 'master' of github.com:budibase/budibase into add-lints-for-only

This commit is contained in:
Sam Rose 2024-03-19 09:41:42 +00:00
commit 03720b8ea8
No known key found for this signature in database
9 changed files with 71 additions and 149 deletions

View file

@ -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)})`
}

View file

@ -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", () => {

View file

@ -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)

View file

@ -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"`,
})
})
})
})

View file

@ -143,16 +143,12 @@ export abstract class TestAPI {
return await request
}
protected _request = async <T>(
method: Method,
url: string,
opts?: RequestOpts
): Promise<T> => {
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 <T>(
method: Method,
url: string,
opts?: RequestOpts
): Promise<T> => {
return this._checkResponse(
await this._requestRaw(method, url, opts),
opts?.expectations
).body
}
}

View file

@ -31,6 +31,6 @@ export class LegacyViewAPI extends TestAPI {
query: { view: viewName, format },
expectations,
})
return response.text
return this._checkResponse(response, expectations).text
}
}

View file

@ -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
}

View file

@ -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"
}
}

View file

@ -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"