Merge branch 'master' into BUDI-8084/single-attachment-column-setting
This commit is contained in:
commit
a276c71ad9
|
@ -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)})`
|
||||
}
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"`,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,6 @@ export class LegacyViewAPI extends TestAPI {
|
|||
query: { view: viewName, format },
|
||||
expectations,
|
||||
})
|
||||
return response.text
|
||||
return this._checkResponse(response, expectations).text
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue