2023-09-06 12:51:08 +02:00
|
|
|
import { databaseTestProviders } from "../../../integrations/tests/utils"
|
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
import tk from "timekeeper"
|
|
|
|
import { outputProcessing } from "../../../utilities/rowProcessor"
|
|
|
|
import * as setup from "./utilities"
|
2023-09-04 14:12:37 +02:00
|
|
|
import { context, roles, tenancy } from "@budibase/backend-core"
|
2023-07-13 12:17:24 +02:00
|
|
|
import { quotas } from "@budibase/pro"
|
|
|
|
import {
|
2023-09-05 12:09:11 +02:00
|
|
|
Datasource,
|
2023-08-11 16:00:50 +02:00
|
|
|
FieldType,
|
2022-09-28 09:56:45 +02:00
|
|
|
MonthlyQuotaName,
|
2023-09-04 14:12:37 +02:00
|
|
|
PermissionLevel,
|
2023-08-11 16:00:50 +02:00
|
|
|
QuotaUsageType,
|
2023-07-13 12:17:24 +02:00
|
|
|
Row,
|
2023-09-08 10:04:49 +02:00
|
|
|
SaveRowRequest,
|
2023-09-05 12:09:11 +02:00
|
|
|
SaveTableRequest,
|
2023-07-18 15:34:25 +02:00
|
|
|
SortOrder,
|
2023-08-11 16:00:50 +02:00
|
|
|
SortType,
|
|
|
|
StaticQuotaName,
|
|
|
|
Table,
|
2023-07-13 12:17:24 +02:00
|
|
|
} from "@budibase/types"
|
2023-07-25 16:32:26 +02:00
|
|
|
import {
|
2023-09-08 16:31:47 +02:00
|
|
|
expectAnyExternalColsAttributes,
|
2023-07-25 16:32:26 +02:00
|
|
|
expectAnyInternalColsAttributes,
|
|
|
|
generator,
|
2023-09-04 14:12:37 +02:00
|
|
|
mocks,
|
2023-07-25 16:32:26 +02:00
|
|
|
structures,
|
|
|
|
} from "@budibase/backend-core/tests"
|
2021-03-26 15:11:24 +01:00
|
|
|
|
2023-08-11 16:00:50 +02:00
|
|
|
const timestamp = new Date("2023-01-26T11:48:57.597Z").toISOString()
|
|
|
|
tk.freeze(timestamp)
|
|
|
|
|
|
|
|
const { basicRow } = setup.structures
|
|
|
|
|
2023-09-05 12:09:11 +02:00
|
|
|
describe.each([
|
|
|
|
["internal", undefined],
|
|
|
|
["postgres", databaseTestProviders.postgres],
|
|
|
|
])("/rows (%s)", (_, dsProvider) => {
|
2023-09-08 10:04:49 +02:00
|
|
|
const isInternal = !dsProvider
|
|
|
|
|
2021-03-05 13:11:44 +01:00
|
|
|
let request = setup.getRequest()
|
|
|
|
let config = setup.getConfig()
|
2023-07-13 12:17:24 +02:00
|
|
|
let table: Table
|
2020-04-22 17:35:20 +02:00
|
|
|
|
2021-03-05 13:11:44 +01:00
|
|
|
afterAll(setup.afterAll)
|
2020-04-22 17:35:20 +02:00
|
|
|
|
2023-02-03 18:55:40 +01:00
|
|
|
beforeAll(async () => {
|
2021-03-05 13:11:44 +01:00
|
|
|
await config.init()
|
2023-02-03 18:55:40 +01:00
|
|
|
|
2023-09-05 12:09:11 +02:00
|
|
|
if (dsProvider) {
|
2023-09-12 19:18:19 +02:00
|
|
|
await config.createDatasource({
|
|
|
|
datasource: await dsProvider.getDsConfig(),
|
|
|
|
})
|
2023-09-05 12:09:11 +02:00
|
|
|
}
|
2023-09-12 19:18:19 +02:00
|
|
|
})
|
2023-09-08 15:46:10 +02:00
|
|
|
|
2023-09-12 19:18:19 +02:00
|
|
|
const generateTableConfig: () => SaveTableRequest = () => {
|
2023-09-08 15:46:10 +02:00
|
|
|
return {
|
2023-09-12 18:09:42 +02:00
|
|
|
name: generator.word(),
|
2023-09-08 15:46:10 +02:00
|
|
|
type: "table",
|
|
|
|
primary: ["id"],
|
2023-09-12 18:09:09 +02:00
|
|
|
primaryDisplay: "name",
|
2023-09-08 15:46:10 +02:00
|
|
|
schema: {
|
|
|
|
id: {
|
|
|
|
type: FieldType.AUTO,
|
|
|
|
name: "id",
|
|
|
|
autocolumn: true,
|
|
|
|
constraints: {
|
|
|
|
presence: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
name: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "name",
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "description",
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-09-05 12:09:11 +02:00
|
|
|
|
2023-09-08 15:46:10 +02:00
|
|
|
beforeEach(async () => {
|
|
|
|
mocks.licenses.useCloudFree()
|
2023-09-12 19:18:19 +02:00
|
|
|
const tableConfig = generateTableConfig()
|
|
|
|
table = await config.createTable(tableConfig)
|
2020-05-27 18:23:01 +02:00
|
|
|
})
|
2020-04-24 19:02:51 +02:00
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
const loadRow = async (id: string, tbl_Id: string, status = 200) =>
|
2020-09-10 10:36:14 +02:00
|
|
|
await request
|
2023-05-10 13:36:01 +02:00
|
|
|
.get(`/api/${tbl_Id}/rows/${id}`)
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2021-03-10 18:55:42 +01:00
|
|
|
.expect(status)
|
2020-09-10 10:36:14 +02:00
|
|
|
|
2022-05-11 12:32:53 +02:00
|
|
|
const getRowUsage = async () => {
|
2023-07-13 12:17:24 +02:00
|
|
|
const { total } = await config.doInContext(null, () =>
|
|
|
|
quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS)
|
|
|
|
)
|
2022-09-28 12:57:58 +02:00
|
|
|
return total
|
2022-05-11 12:32:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const getQueryUsage = async () => {
|
2023-07-13 12:17:24 +02:00
|
|
|
const { total } = await config.doInContext(null, () =>
|
|
|
|
quotas.getCurrentUsageValues(
|
|
|
|
QuotaUsageType.MONTHLY,
|
|
|
|
MonthlyQuotaName.QUERIES
|
|
|
|
)
|
|
|
|
)
|
2022-05-26 18:31:57 +02:00
|
|
|
return total
|
2022-05-11 12:32:53 +02:00
|
|
|
}
|
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
const assertRowUsage = async (expected: number) => {
|
2022-05-11 12:32:53 +02:00
|
|
|
const usage = await getRowUsage()
|
|
|
|
expect(usage).toBe(expected)
|
|
|
|
}
|
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
const assertQueryUsage = async (expected: number) => {
|
2022-05-11 12:32:53 +02:00
|
|
|
const usage = await getQueryUsage()
|
|
|
|
expect(usage).toBe(expected)
|
|
|
|
}
|
2020-09-10 10:36:14 +02:00
|
|
|
|
2023-09-08 13:52:17 +02:00
|
|
|
const createRow = (tableId = table._id!, row?: SaveRowRequest) =>
|
|
|
|
config.api.row.save(tableId, row || basicRow(table._id!))
|
2023-09-06 13:03:35 +02:00
|
|
|
|
2023-09-08 15:29:05 +02:00
|
|
|
const defaultRowFields = isInternal
|
|
|
|
? {
|
|
|
|
type: "row",
|
|
|
|
createdAt: timestamp,
|
|
|
|
updatedAt: timestamp,
|
2023-09-08 10:04:49 +02:00
|
|
|
}
|
2023-09-08 15:29:05 +02:00
|
|
|
: undefined
|
2023-09-08 10:04:49 +02:00
|
|
|
|
2023-09-08 15:29:05 +02:00
|
|
|
describe("save, load, update", () => {
|
2020-10-09 20:10:28 +02:00
|
|
|
it("returns a success message when the row is created", async () => {
|
2022-09-28 13:33:39 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
|
|
|
const res = await request
|
2023-09-12 19:18:19 +02:00
|
|
|
.post(`/api/${config.table!._id}/rows`)
|
|
|
|
.send(basicRow(config.table!._id!))
|
2022-09-28 13:33:39 +02:00
|
|
|
.set(config.defaultHeaders())
|
2023-07-13 12:17:24 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2022-09-28 13:33:39 +02:00
|
|
|
.expect(200)
|
2023-07-13 12:17:24 +02:00
|
|
|
expect((res as any).res.statusMessage).toEqual(
|
|
|
|
`${table.name} saved successfully`
|
|
|
|
)
|
2022-09-28 13:33:39 +02:00
|
|
|
expect(res.body.name).toEqual("Test Contact")
|
|
|
|
expect(res.body._rev).toBeDefined()
|
|
|
|
await assertRowUsage(rowUsage + 1)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
|
|
|
|
2023-05-15 20:22:22 +02:00
|
|
|
it("Increment row autoId per create row request", async () => {
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-09-12 19:18:19 +02:00
|
|
|
const tableConfig = generateTableConfig()
|
2023-05-15 20:22:22 +02:00
|
|
|
const newTable = await config.createTable({
|
2023-09-12 19:03:24 +02:00
|
|
|
...tableConfig,
|
2023-05-15 20:22:22 +02:00
|
|
|
name: "TestTableAuto",
|
|
|
|
schema: {
|
2023-09-12 19:03:24 +02:00
|
|
|
...tableConfig.schema,
|
2023-05-15 20:22:22 +02:00
|
|
|
"Row ID": {
|
|
|
|
name: "Row ID",
|
2023-07-13 12:17:24 +02:00
|
|
|
type: FieldType.NUMBER,
|
2023-05-15 20:22:22 +02:00
|
|
|
subtype: "autoID",
|
|
|
|
icon: "ri-magic-line",
|
|
|
|
autocolumn: true,
|
|
|
|
constraints: {
|
|
|
|
type: "number",
|
2023-09-08 17:42:54 +02:00
|
|
|
presence: true,
|
2023-05-15 20:22:22 +02:00
|
|
|
numericality: {
|
|
|
|
greaterThanOrEqualTo: "",
|
|
|
|
lessThanOrEqualTo: "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-07-13 12:17:24 +02:00
|
|
|
},
|
2023-05-15 20:22:22 +02:00
|
|
|
})
|
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
const ids = [1, 2, 3]
|
2023-05-15 20:22:22 +02:00
|
|
|
|
|
|
|
// Performing several create row requests should increment the autoID fields accordingly
|
2023-07-13 12:17:24 +02:00
|
|
|
const createRow = async (id: number) => {
|
2023-05-15 20:22:22 +02:00
|
|
|
const res = await request
|
|
|
|
.post(`/api/${newTable._id}/rows`)
|
|
|
|
.send({
|
2023-07-13 12:17:24 +02:00
|
|
|
name: "row_" + id,
|
2023-05-15 20:22:22 +02:00
|
|
|
})
|
|
|
|
.set(config.defaultHeaders())
|
2023-07-13 12:17:24 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2023-05-15 20:22:22 +02:00
|
|
|
.expect(200)
|
2023-07-13 12:17:24 +02:00
|
|
|
expect((res as any).res.statusMessage).toEqual(
|
|
|
|
`${newTable.name} saved successfully`
|
|
|
|
)
|
2023-05-15 20:22:22 +02:00
|
|
|
expect(res.body.name).toEqual("row_" + id)
|
|
|
|
expect(res.body._rev).toBeDefined()
|
|
|
|
expect(res.body["Row ID"]).toEqual(id)
|
|
|
|
}
|
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
for (let i = 0; i < ids.length; i++) {
|
2023-05-15 20:22:22 +02:00
|
|
|
await createRow(ids[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
await assertRowUsage(rowUsage + ids.length)
|
|
|
|
await assertQueryUsage(queryUsage + ids.length)
|
|
|
|
})
|
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
it("updates a row successfully", async () => {
|
2023-09-06 13:03:35 +02:00
|
|
|
const existing = await createRow()
|
2022-09-28 13:33:39 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
2020-04-24 19:02:51 +02:00
|
|
|
|
2020-04-22 17:35:20 +02:00
|
|
|
const res = await request
|
2020-10-09 20:10:28 +02:00
|
|
|
.post(`/api/${table._id}/rows`)
|
2020-04-24 19:02:51 +02:00
|
|
|
.send({
|
2020-05-14 16:12:30 +02:00
|
|
|
_id: existing._id,
|
|
|
|
_rev: existing._rev,
|
2020-10-09 19:49:23 +02:00
|
|
|
tableId: table._id,
|
2020-04-24 19:02:51 +02:00
|
|
|
name: "Updated Name",
|
|
|
|
})
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2020-04-22 17:35:20 +02:00
|
|
|
.expect(200)
|
2022-09-28 09:56:45 +02:00
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
expect((res as any).res.statusMessage).toEqual(
|
2022-09-28 09:56:45 +02:00
|
|
|
`${table.name} updated successfully.`
|
|
|
|
)
|
2020-05-14 16:12:30 +02:00
|
|
|
expect(res.body.name).toEqual("Updated Name")
|
2022-09-28 13:33:39 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
it("should load a row", async () => {
|
2023-09-08 10:04:49 +02:00
|
|
|
const existing = await createRow()
|
2022-05-11 12:32:53 +02:00
|
|
|
const queryUsage = await getQueryUsage()
|
2020-04-24 19:02:51 +02:00
|
|
|
|
2020-04-22 17:35:20 +02:00
|
|
|
const res = await request
|
2020-10-09 20:10:28 +02:00
|
|
|
.get(`/api/${table._id}/rows/${existing._id}`)
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2020-04-22 17:35:20 +02:00
|
|
|
.expect(200)
|
2020-04-24 19:02:51 +02:00
|
|
|
|
|
|
|
expect(res.body).toEqual({
|
2023-09-12 19:18:19 +02:00
|
|
|
...existing,
|
2023-09-08 15:29:05 +02:00
|
|
|
...defaultRowFields,
|
2020-04-24 19:02:51 +02:00
|
|
|
})
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
it("should list all rows for given tableId", async () => {
|
|
|
|
const newRow = {
|
2020-10-09 19:49:23 +02:00
|
|
|
tableId: table._id,
|
2020-04-24 19:02:51 +02:00
|
|
|
name: "Second Contact",
|
2022-09-28 09:56:45 +02:00
|
|
|
status: "new",
|
2020-04-24 19:02:51 +02:00
|
|
|
}
|
2023-09-12 19:18:19 +02:00
|
|
|
const firstRow = await createRow()
|
2023-09-08 13:52:17 +02:00
|
|
|
await createRow(table._id, newRow)
|
2022-05-11 12:32:53 +02:00
|
|
|
const queryUsage = await getQueryUsage()
|
2020-04-22 17:35:20 +02:00
|
|
|
|
|
|
|
const res = await request
|
2020-10-09 20:10:28 +02:00
|
|
|
.get(`/api/${table._id}/rows`)
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2020-04-22 17:35:20 +02:00
|
|
|
.expect(200)
|
|
|
|
|
2020-05-14 16:12:30 +02:00
|
|
|
expect(res.body.length).toBe(2)
|
2023-07-13 12:17:24 +02:00
|
|
|
expect(res.body.find((r: Row) => r.name === newRow.name)).toBeDefined()
|
2023-09-12 19:18:19 +02:00
|
|
|
expect(res.body.find((r: Row) => r.name === firstRow.name)).toBeDefined()
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
it("load should return 404 when row does not exist", async () => {
|
2023-09-08 10:04:49 +02:00
|
|
|
await createRow()
|
2022-05-11 12:32:53 +02:00
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-09-08 10:04:49 +02:00
|
|
|
await config.api.row.get(table._id!, "1234567", {
|
|
|
|
expectStatus: 404,
|
|
|
|
})
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertQueryUsage(queryUsage) // no change
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
2020-10-05 18:28:23 +02:00
|
|
|
|
2023-09-12 11:02:44 +02:00
|
|
|
isInternal &&
|
|
|
|
it("row values are coerced", async () => {
|
|
|
|
const str = {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "str",
|
|
|
|
constraints: { type: "string", presence: false },
|
|
|
|
}
|
|
|
|
const attachment = {
|
|
|
|
type: FieldType.ATTACHMENT,
|
|
|
|
name: "attachment",
|
|
|
|
constraints: { type: "array", presence: false },
|
|
|
|
}
|
|
|
|
const bool = {
|
|
|
|
type: FieldType.BOOLEAN,
|
|
|
|
name: "boolean",
|
|
|
|
constraints: { type: "boolean", presence: false },
|
|
|
|
}
|
|
|
|
const number = {
|
|
|
|
type: FieldType.NUMBER,
|
|
|
|
name: "str",
|
|
|
|
constraints: { type: "number", presence: false },
|
|
|
|
}
|
|
|
|
const datetime = {
|
|
|
|
type: FieldType.DATETIME,
|
|
|
|
name: "datetime",
|
2023-07-13 12:17:24 +02:00
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
presence: false,
|
2023-09-12 11:02:44 +02:00
|
|
|
datetime: { earliest: "", latest: "" },
|
2023-07-13 12:17:24 +02:00
|
|
|
},
|
2023-09-12 11:02:44 +02:00
|
|
|
}
|
|
|
|
const arrayField = {
|
|
|
|
type: FieldType.ARRAY,
|
|
|
|
constraints: {
|
|
|
|
type: "array",
|
|
|
|
presence: false,
|
|
|
|
inclusion: ["One", "Two", "Three"],
|
2023-07-13 12:17:24 +02:00
|
|
|
},
|
2023-09-12 11:02:44 +02:00
|
|
|
name: "Sample Tags",
|
|
|
|
sortable: false,
|
|
|
|
}
|
|
|
|
const optsField = {
|
|
|
|
fieldName: "Sample Opts",
|
|
|
|
name: "Sample Opts",
|
|
|
|
type: FieldType.OPTIONS,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
presence: false,
|
|
|
|
inclusion: ["Alpha", "Beta", "Gamma"],
|
|
|
|
},
|
|
|
|
},
|
2023-09-12 19:18:19 +02:00
|
|
|
table = await config.createTable({
|
2023-09-12 11:02:44 +02:00
|
|
|
name: "TestTable2",
|
|
|
|
type: "table",
|
|
|
|
schema: {
|
|
|
|
name: str,
|
|
|
|
stringUndefined: str,
|
|
|
|
stringNull: str,
|
|
|
|
stringString: str,
|
|
|
|
numberEmptyString: number,
|
|
|
|
numberNull: number,
|
|
|
|
numberUndefined: number,
|
|
|
|
numberString: number,
|
|
|
|
numberNumber: number,
|
|
|
|
datetimeEmptyString: datetime,
|
|
|
|
datetimeNull: datetime,
|
|
|
|
datetimeUndefined: datetime,
|
|
|
|
datetimeString: datetime,
|
|
|
|
datetimeDate: datetime,
|
|
|
|
boolNull: bool,
|
|
|
|
boolEmpty: bool,
|
|
|
|
boolUndefined: bool,
|
|
|
|
boolString: bool,
|
|
|
|
boolBool: bool,
|
|
|
|
attachmentNull: attachment,
|
|
|
|
attachmentUndefined: attachment,
|
|
|
|
attachmentEmpty: attachment,
|
|
|
|
attachmentEmptyArrayStr: attachment,
|
|
|
|
arrayFieldEmptyArrayStr: arrayField,
|
|
|
|
arrayFieldArrayStrKnown: arrayField,
|
|
|
|
arrayFieldNull: arrayField,
|
|
|
|
arrayFieldUndefined: arrayField,
|
|
|
|
optsFieldEmptyStr: optsField,
|
|
|
|
optsFieldUndefined: optsField,
|
|
|
|
optsFieldNull: optsField,
|
|
|
|
optsFieldStrKnown: optsField,
|
|
|
|
},
|
|
|
|
})
|
2020-10-05 18:28:23 +02:00
|
|
|
|
2023-09-12 19:18:19 +02:00
|
|
|
const row = {
|
2023-09-12 11:02:44 +02:00
|
|
|
name: "Test Row",
|
|
|
|
stringUndefined: undefined,
|
|
|
|
stringNull: null,
|
|
|
|
stringString: "i am a string",
|
|
|
|
numberEmptyString: "",
|
|
|
|
numberNull: null,
|
|
|
|
numberUndefined: undefined,
|
|
|
|
numberString: "123",
|
|
|
|
numberNumber: 123,
|
|
|
|
datetimeEmptyString: "",
|
|
|
|
datetimeNull: null,
|
|
|
|
datetimeUndefined: undefined,
|
|
|
|
datetimeString: "1984-04-20T00:00:00.000Z",
|
|
|
|
datetimeDate: new Date("1984-04-20"),
|
|
|
|
boolNull: null,
|
|
|
|
boolEmpty: "",
|
|
|
|
boolUndefined: undefined,
|
|
|
|
boolString: "true",
|
|
|
|
boolBool: true,
|
|
|
|
tableId: table._id,
|
|
|
|
attachmentNull: null,
|
|
|
|
attachmentUndefined: undefined,
|
|
|
|
attachmentEmpty: "",
|
|
|
|
attachmentEmptyArrayStr: "[]",
|
|
|
|
arrayFieldEmptyArrayStr: "[]",
|
|
|
|
arrayFieldUndefined: undefined,
|
|
|
|
arrayFieldNull: null,
|
|
|
|
arrayFieldArrayStrKnown: "['One']",
|
|
|
|
optsFieldEmptyStr: "",
|
|
|
|
optsFieldUndefined: undefined,
|
|
|
|
optsFieldNull: null,
|
|
|
|
optsFieldStrKnown: "Alpha",
|
|
|
|
}
|
|
|
|
|
|
|
|
const createdRow = await config.createRow(row)
|
|
|
|
const id = createdRow._id!
|
|
|
|
|
|
|
|
const saved = (await loadRow(id, table._id!)).body
|
|
|
|
|
|
|
|
expect(saved.stringUndefined).toBe(undefined)
|
|
|
|
expect(saved.stringNull).toBe("")
|
|
|
|
expect(saved.stringString).toBe("i am a string")
|
|
|
|
expect(saved.numberEmptyString).toBe(null)
|
|
|
|
expect(saved.numberNull).toBe(null)
|
|
|
|
expect(saved.numberUndefined).toBe(undefined)
|
|
|
|
expect(saved.numberString).toBe(123)
|
|
|
|
expect(saved.numberNumber).toBe(123)
|
|
|
|
expect(saved.datetimeEmptyString).toBe(null)
|
|
|
|
expect(saved.datetimeNull).toBe(null)
|
|
|
|
expect(saved.datetimeUndefined).toBe(undefined)
|
|
|
|
expect(saved.datetimeString).toBe(
|
|
|
|
new Date(row.datetimeString).toISOString()
|
|
|
|
)
|
|
|
|
expect(saved.datetimeDate).toBe(row.datetimeDate.toISOString())
|
|
|
|
expect(saved.boolNull).toBe(null)
|
|
|
|
expect(saved.boolEmpty).toBe(null)
|
|
|
|
expect(saved.boolUndefined).toBe(undefined)
|
|
|
|
expect(saved.boolString).toBe(true)
|
|
|
|
expect(saved.boolBool).toBe(true)
|
|
|
|
expect(saved.attachmentNull).toEqual([])
|
|
|
|
expect(saved.attachmentUndefined).toBe(undefined)
|
|
|
|
expect(saved.attachmentEmpty).toEqual([])
|
|
|
|
expect(saved.attachmentEmptyArrayStr).toEqual([])
|
|
|
|
expect(saved.arrayFieldEmptyArrayStr).toEqual([])
|
|
|
|
expect(saved.arrayFieldNull).toEqual([])
|
|
|
|
expect(saved.arrayFieldUndefined).toEqual(undefined)
|
|
|
|
expect(saved.optsFieldEmptyStr).toEqual(null)
|
|
|
|
expect(saved.optsFieldUndefined).toEqual(undefined)
|
|
|
|
expect(saved.optsFieldNull).toEqual(null)
|
|
|
|
expect(saved.arrayFieldArrayStrKnown).toEqual(["One"])
|
|
|
|
expect(saved.optsFieldStrKnown).toEqual("Alpha")
|
|
|
|
})
|
2020-04-22 17:35:20 +02:00
|
|
|
})
|
2020-05-28 16:39:29 +02:00
|
|
|
|
2023-08-11 16:00:50 +02:00
|
|
|
describe("view save", () => {
|
2023-09-12 19:03:24 +02:00
|
|
|
async function orderTable(): Promise<Table> {
|
2023-08-11 16:00:50 +02:00
|
|
|
return {
|
|
|
|
name: "orders",
|
2023-09-12 09:57:47 +02:00
|
|
|
primary: ["OrderID"],
|
2023-08-11 16:00:50 +02:00
|
|
|
schema: {
|
|
|
|
Country: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "Country",
|
|
|
|
},
|
|
|
|
OrderID: {
|
2023-09-12 09:57:47 +02:00
|
|
|
type: FieldType.NUMBER,
|
2023-08-11 16:00:50 +02:00
|
|
|
name: "OrderID",
|
|
|
|
},
|
|
|
|
Story: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "Story",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it("views have extra data trimmed", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await orderTable())
|
2023-08-11 16:00:50 +02:00
|
|
|
|
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
schema: {
|
2023-09-12 09:52:46 +02:00
|
|
|
Country: {
|
|
|
|
visible: true,
|
|
|
|
},
|
|
|
|
OrderID: {
|
|
|
|
visible: true,
|
|
|
|
},
|
2023-08-11 16:00:50 +02:00
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2023-09-12 09:52:46 +02:00
|
|
|
const createRowResponse = await config.api.row.save(
|
|
|
|
createViewResponse.id,
|
|
|
|
{
|
|
|
|
OrderID: "1111",
|
|
|
|
Country: "Aussy",
|
|
|
|
Story: "aaaaa",
|
|
|
|
}
|
|
|
|
)
|
2023-08-11 16:00:50 +02:00
|
|
|
|
2023-09-12 09:52:46 +02:00
|
|
|
const row = await config.api.row.get(table._id!, createRowResponse._id!)
|
2023-08-11 16:00:50 +02:00
|
|
|
expect(row.body.Story).toBeUndefined()
|
2023-09-12 09:52:46 +02:00
|
|
|
expect(row.body).toEqual({
|
|
|
|
...defaultRowFields,
|
2023-09-12 09:57:47 +02:00
|
|
|
OrderID: 1111,
|
2023-09-12 09:52:46 +02:00
|
|
|
Country: "Aussy",
|
2023-09-12 09:57:47 +02:00
|
|
|
_id: createRowResponse._id,
|
2023-09-12 09:52:46 +02:00
|
|
|
_rev: createRowResponse._rev,
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-08-11 16:00:50 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-09-10 10:36:14 +02:00
|
|
|
describe("patch", () => {
|
|
|
|
it("should update only the fields that are supplied", async () => {
|
2023-09-08 10:12:46 +02:00
|
|
|
const existing = await createRow()
|
2020-09-10 10:36:14 +02:00
|
|
|
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-07-26 12:29:19 +02:00
|
|
|
const res = await config.api.row.patch(table._id!, {
|
2023-07-26 13:53:35 +02:00
|
|
|
_id: existing._id!,
|
|
|
|
_rev: existing._rev!,
|
|
|
|
tableId: table._id!,
|
2023-07-26 12:29:19 +02:00
|
|
|
name: "Updated Name",
|
|
|
|
})
|
2022-09-28 09:56:45 +02:00
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
expect((res as any).res.statusMessage).toEqual(
|
2022-09-28 09:56:45 +02:00
|
|
|
`${table.name} updated successfully.`
|
|
|
|
)
|
2020-09-10 10:36:14 +02:00
|
|
|
expect(res.body.name).toEqual("Updated Name")
|
2020-09-11 10:29:23 +02:00
|
|
|
expect(res.body.description).toEqual(existing.description)
|
2020-09-10 10:36:14 +02:00
|
|
|
|
2023-07-13 12:17:24 +02:00
|
|
|
const savedRow = await loadRow(res.body._id, table._id!)
|
2020-09-10 10:36:14 +02:00
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
expect(savedRow.body.description).toEqual(existing.description)
|
|
|
|
expect(savedRow.body.name).toEqual("Updated Name")
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage + 2) // account for the second load
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
it("should throw an error when given improper types", async () => {
|
2023-09-08 10:12:46 +02:00
|
|
|
const existing = await createRow()
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-07-26 12:29:19 +02:00
|
|
|
await config.api.row.patch(
|
|
|
|
table._id!,
|
|
|
|
{
|
2023-07-26 13:53:35 +02:00
|
|
|
_id: existing._id!,
|
|
|
|
_rev: existing._rev!,
|
|
|
|
tableId: table._id!,
|
2021-03-10 18:55:42 +01:00
|
|
|
name: 1,
|
2023-07-26 12:29:19 +02:00
|
|
|
},
|
|
|
|
{ expectStatus: 400 }
|
|
|
|
)
|
2022-05-11 12:32:53 +02:00
|
|
|
|
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage)
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("destroy", () => {
|
|
|
|
it("should be able to delete a row", async () => {
|
2023-09-08 13:52:17 +02:00
|
|
|
const createdRow = await createRow()
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
const res = await request
|
2021-06-17 17:52:52 +02:00
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send({
|
2022-09-28 09:56:45 +02:00
|
|
|
rows: [createdRow],
|
2021-06-17 17:52:52 +02:00
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2021-03-10 18:55:42 +01:00
|
|
|
.expect(200)
|
2021-06-17 17:52:52 +02:00
|
|
|
expect(res.body[0]._id).toEqual(createdRow._id)
|
2022-09-28 09:56:45 +02:00
|
|
|
await assertRowUsage(rowUsage - 1)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2020-09-10 10:36:14 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-05-28 16:39:29 +02:00
|
|
|
describe("validate", () => {
|
2020-10-09 20:10:28 +02:00
|
|
|
it("should return no errors on valid row", async () => {
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
const res = await request
|
2020-10-09 20:10:28 +02:00
|
|
|
.post(`/api/${table._id}/rows/validate`)
|
2020-05-28 16:39:29 +02:00
|
|
|
.send({ name: "ivan" })
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2020-05-28 16:39:29 +02:00
|
|
|
.expect(200)
|
2022-09-28 09:56:45 +02:00
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
expect(res.body.valid).toBe(true)
|
|
|
|
expect(Object.keys(res.body.errors)).toEqual([])
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage)
|
2020-05-28 16:39:29 +02:00
|
|
|
})
|
|
|
|
|
2020-10-09 20:10:28 +02:00
|
|
|
it("should errors on invalid row", async () => {
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
const res = await request
|
2020-10-09 20:10:28 +02:00
|
|
|
.post(`/api/${table._id}/rows/validate`)
|
2020-05-28 16:39:29 +02:00
|
|
|
.send({ name: 1 })
|
2021-03-04 14:07:33 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2020-05-28 16:39:29 +02:00
|
|
|
.expect(200)
|
2022-09-28 09:56:45 +02:00
|
|
|
|
2023-09-08 10:22:24 +02:00
|
|
|
if (isInternal) {
|
|
|
|
expect(res.body.valid).toBe(false)
|
|
|
|
expect(Object.keys(res.body.errors)).toEqual(["name"])
|
|
|
|
} else {
|
|
|
|
// Validation for external is not implemented, so it will always return valid
|
|
|
|
expect(res.body.valid).toBe(true)
|
|
|
|
expect(Object.keys(res.body.errors)).toEqual([])
|
|
|
|
}
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage)
|
2020-05-28 16:39:29 +02:00
|
|
|
})
|
|
|
|
})
|
2021-02-02 12:46:10 +01:00
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
describe("bulkDelete", () => {
|
|
|
|
it("should be able to delete a bulk set of rows", async () => {
|
2023-09-08 10:34:45 +02:00
|
|
|
const row1 = await createRow()
|
|
|
|
const row2 = await createRow()
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
const res = await request
|
2021-06-17 17:52:52 +02:00
|
|
|
.delete(`/api/${table._id}/rows`)
|
2021-03-10 18:55:42 +01:00
|
|
|
.send({
|
2022-09-28 09:56:45 +02:00
|
|
|
rows: [row1, row2],
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2021-03-10 18:55:42 +01:00
|
|
|
.expect(200)
|
2022-05-11 12:32:53 +02:00
|
|
|
|
2021-03-10 18:55:42 +01:00
|
|
|
expect(res.body.length).toEqual(2)
|
2023-07-13 12:17:24 +02:00
|
|
|
await loadRow(row1._id!, table._id!, 404)
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertRowUsage(rowUsage - 2)
|
2022-09-28 09:56:45 +02:00
|
|
|
await assertQueryUsage(queryUsage + 1)
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
2023-07-24 10:08:10 +02:00
|
|
|
|
|
|
|
it("should be able to delete a variety of row set types", async () => {
|
2023-09-08 10:34:45 +02:00
|
|
|
const row1 = await createRow()
|
|
|
|
const row2 = await createRow()
|
|
|
|
const row3 = await createRow()
|
2023-07-24 10:08:10 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
|
|
|
const res = await request
|
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send({
|
|
|
|
rows: [row1, row2._id, { _id: row3._id }],
|
|
|
|
})
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
|
|
|
|
expect(res.body.length).toEqual(3)
|
|
|
|
await loadRow(row1._id!, table._id!, 404)
|
|
|
|
await assertRowUsage(rowUsage - 3)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should accept a valid row object and delete the row", async () => {
|
2023-09-08 10:34:45 +02:00
|
|
|
const row1 = await createRow()
|
2023-07-24 10:08:10 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
|
|
|
const res = await request
|
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send(row1)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
|
|
|
|
expect(res.body.id).toEqual(row1._id)
|
|
|
|
await loadRow(row1._id!, table._id!, 404)
|
|
|
|
await assertRowUsage(rowUsage - 1)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
})
|
2023-07-24 16:03:13 +02:00
|
|
|
|
|
|
|
it("Should ignore malformed/invalid delete requests", async () => {
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
|
|
|
const res = await request
|
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send({ not: "valid" })
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(400)
|
|
|
|
|
|
|
|
expect(res.body.message).toEqual("Invalid delete rows request")
|
|
|
|
|
|
|
|
const res2 = await request
|
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send({ rows: 123 })
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(400)
|
|
|
|
|
|
|
|
expect(res2.body.message).toEqual("Invalid delete rows request")
|
|
|
|
|
|
|
|
const res3 = await request
|
|
|
|
.delete(`/api/${table._id}/rows`)
|
|
|
|
.send("invalid")
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(400)
|
|
|
|
|
|
|
|
expect(res3.body.message).toEqual("Invalid delete rows request")
|
|
|
|
|
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage)
|
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
// Legacy views are not available for external
|
|
|
|
isInternal &&
|
|
|
|
describe("fetchView", () => {
|
|
|
|
it("should be able to fetch tables contents via 'view'", async () => {
|
|
|
|
const row = await createRow()
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
2022-05-11 12:32:53 +02:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
const res = await request
|
|
|
|
.get(`/api/views/${table._id}`)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
expect(res.body.length).toEqual(1)
|
|
|
|
expect(res.body[0]._id).toEqual(row._id)
|
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
it("should throw an error if view doesn't exist", async () => {
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
2022-05-11 12:32:53 +02:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
await request
|
|
|
|
.get(`/api/views/derp`)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect(404)
|
2022-05-11 12:32:53 +02:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage)
|
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
it("should be able to run on a view", async () => {
|
|
|
|
const view = await config.createLegacyView({
|
|
|
|
tableId: table._id!,
|
|
|
|
name: "ViewTest",
|
|
|
|
filters: [],
|
|
|
|
schema: {},
|
|
|
|
})
|
|
|
|
const row = await createRow()
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
2022-05-11 12:32:53 +02:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
const res = await request
|
|
|
|
.get(`/api/views/${view.name}`)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
expect(res.body.length).toEqual(1)
|
|
|
|
expect(res.body[0]._id).toEqual(row._id)
|
2021-03-10 18:55:42 +01:00
|
|
|
|
2023-09-08 10:43:19 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
})
|
2022-05-11 12:32:53 +02:00
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
|
|
|
|
describe("fetchEnrichedRows", () => {
|
2021-02-02 12:46:10 +01:00
|
|
|
it("should allow enriching some linked rows", async () => {
|
2023-09-08 15:09:50 +02:00
|
|
|
const { linkedTable, firstRow, secondRow } = await tenancy.doInTenant(
|
2023-07-13 12:17:24 +02:00
|
|
|
config.getTenantId(),
|
2022-09-28 09:56:45 +02:00
|
|
|
async () => {
|
2023-09-08 15:09:50 +02:00
|
|
|
const linkedTable = await config.createLinkedTable({
|
2023-09-12 18:09:42 +02:00
|
|
|
name: generator.word(),
|
2023-09-08 15:09:50 +02:00
|
|
|
type: "table",
|
|
|
|
primary: ["id"],
|
2023-09-12 18:09:09 +02:00
|
|
|
primaryDisplay: "id",
|
2023-09-08 15:09:50 +02:00
|
|
|
schema: {
|
|
|
|
id: {
|
|
|
|
type: FieldType.AUTO,
|
|
|
|
name: "id",
|
|
|
|
autocolumn: true,
|
|
|
|
constraints: {
|
|
|
|
presence: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
const firstRow = await createRow(table._id, {
|
2022-09-28 09:56:45 +02:00
|
|
|
name: "Test Contact",
|
|
|
|
description: "original description",
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-09-08 15:09:50 +02:00
|
|
|
const secondRow = await createRow(linkedTable._id, {
|
2022-09-28 09:56:45 +02:00
|
|
|
name: "Test 2",
|
|
|
|
description: "og desc",
|
|
|
|
link: [{ _id: firstRow._id }],
|
2023-09-08 15:09:50 +02:00
|
|
|
tableId: linkedTable._id,
|
2022-09-28 09:56:45 +02:00
|
|
|
})
|
2023-09-08 15:09:50 +02:00
|
|
|
return { linkedTable, firstRow, secondRow }
|
2022-09-28 09:56:45 +02:00
|
|
|
}
|
|
|
|
)
|
2022-05-11 12:32:53 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
2021-03-10 18:55:42 +01:00
|
|
|
|
|
|
|
// test basic enrichment
|
|
|
|
const resBasic = await request
|
2023-09-08 15:09:50 +02:00
|
|
|
.get(`/api/${linkedTable._id}/rows/${secondRow._id}`)
|
2021-03-10 18:55:42 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2021-03-10 18:55:42 +01:00
|
|
|
.expect(200)
|
2023-09-08 15:09:50 +02:00
|
|
|
expect(resBasic.body.link.length).toBe(1)
|
2023-09-12 18:09:09 +02:00
|
|
|
expect(resBasic.body.link[0]).toEqual({
|
|
|
|
_id: firstRow._id,
|
|
|
|
primaryDisplay: firstRow.name,
|
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
|
|
|
|
// test full enrichment
|
|
|
|
const resEnriched = await request
|
2023-09-08 15:09:50 +02:00
|
|
|
.get(`/api/${linkedTable._id}/${secondRow._id}/enrich`)
|
2021-03-10 18:55:42 +01:00
|
|
|
.set(config.defaultHeaders())
|
2022-09-28 09:56:45 +02:00
|
|
|
.expect("Content-Type", /json/)
|
2021-03-10 18:55:42 +01:00
|
|
|
.expect(200)
|
|
|
|
expect(resEnriched.body.link.length).toBe(1)
|
|
|
|
expect(resEnriched.body.link[0]._id).toBe(firstRow._id)
|
|
|
|
expect(resEnriched.body.link[0].name).toBe("Test Contact")
|
|
|
|
expect(resEnriched.body.link[0].description).toBe("original description")
|
2022-05-11 12:32:53 +02:00
|
|
|
await assertRowUsage(rowUsage)
|
2022-09-28 09:56:45 +02:00
|
|
|
await assertQueryUsage(queryUsage + 2)
|
2021-02-02 12:46:10 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2023-09-12 18:07:50 +02:00
|
|
|
isInternal &&
|
|
|
|
describe("attachments", () => {
|
|
|
|
it("should allow enriching attachment rows", async () => {
|
|
|
|
const table = await config.createAttachmentTable()
|
|
|
|
const attachmentId = `${structures.uuid()}.csv`
|
|
|
|
const row = await createRow(table._id, {
|
|
|
|
name: "test",
|
|
|
|
description: "test",
|
|
|
|
attachment: [
|
|
|
|
{
|
|
|
|
key: `${config.getAppId()}/attachments/${attachmentId}`,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
|
|
|
// the environment needs configured for this
|
|
|
|
await setup.switchToSelfHosted(async () => {
|
|
|
|
return context.doInAppContext(config.getAppId(), async () => {
|
|
|
|
const enriched = await outputProcessing(table, [row])
|
|
|
|
expect((enriched as Row[])[0].attachment[0].url).toBe(
|
|
|
|
`/files/signed/prod-budi-app-assets/${config.getProdAppId()}/attachments/${attachmentId}`
|
|
|
|
)
|
|
|
|
})
|
2022-01-31 18:00:22 +01:00
|
|
|
})
|
2021-03-10 18:55:42 +01:00
|
|
|
})
|
2021-03-04 14:07:33 +01:00
|
|
|
})
|
2022-09-28 09:56:45 +02:00
|
|
|
|
|
|
|
describe("exportData", () => {
|
|
|
|
it("should allow exporting all columns", async () => {
|
2023-09-12 19:03:24 +02:00
|
|
|
const existing = await createRow()
|
2022-09-28 09:56:45 +02:00
|
|
|
const res = await request
|
|
|
|
.post(`/api/${table._id}/rows/exportRows?format=json`)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.send({
|
|
|
|
rows: [existing._id],
|
|
|
|
})
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
const results = JSON.parse(res.text)
|
|
|
|
expect(results.length).toEqual(1)
|
|
|
|
const row = results[0]
|
|
|
|
|
|
|
|
// Ensure all original columns were exported
|
|
|
|
expect(Object.keys(row).length).toBeGreaterThanOrEqual(
|
|
|
|
Object.keys(existing).length
|
|
|
|
)
|
|
|
|
Object.keys(existing).forEach(key => {
|
|
|
|
expect(row[key]).toEqual(existing[key])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should allow exporting only certain columns", async () => {
|
2023-09-12 19:03:24 +02:00
|
|
|
const existing = await createRow()
|
2022-09-28 09:56:45 +02:00
|
|
|
const res = await request
|
|
|
|
.post(`/api/${table._id}/rows/exportRows?format=json`)
|
|
|
|
.set(config.defaultHeaders())
|
|
|
|
.send({
|
|
|
|
rows: [existing._id],
|
|
|
|
columns: ["_id"],
|
|
|
|
})
|
|
|
|
.expect("Content-Type", /json/)
|
|
|
|
.expect(200)
|
|
|
|
const results = JSON.parse(res.text)
|
|
|
|
expect(results.length).toEqual(1)
|
|
|
|
const row = results[0]
|
|
|
|
|
|
|
|
// Ensure only the _id column was exported
|
|
|
|
expect(Object.keys(row).length).toEqual(1)
|
|
|
|
expect(row._id).toEqual(existing._id)
|
|
|
|
})
|
|
|
|
})
|
2023-07-14 10:26:22 +02:00
|
|
|
|
2023-07-31 12:03:03 +02:00
|
|
|
describe("view 2.0", () => {
|
2023-09-08 15:46:10 +02:00
|
|
|
async function userTable(): Promise<Table> {
|
2023-07-31 12:03:03 +02:00
|
|
|
return {
|
2023-09-12 18:09:42 +02:00
|
|
|
name: `users_${generator.word()}`,
|
2023-09-08 15:29:05 +02:00
|
|
|
type: "table",
|
|
|
|
primary: ["id"],
|
2023-07-31 12:03:03 +02:00
|
|
|
schema: {
|
2023-09-08 15:29:05 +02:00
|
|
|
id: {
|
|
|
|
type: FieldType.AUTO,
|
|
|
|
name: "id",
|
|
|
|
autocolumn: true,
|
|
|
|
constraints: {
|
|
|
|
presence: true,
|
|
|
|
},
|
|
|
|
},
|
2023-07-31 12:03:03 +02:00
|
|
|
name: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "name",
|
|
|
|
},
|
|
|
|
surname: {
|
|
|
|
type: FieldType.STRING,
|
2023-09-08 15:29:05 +02:00
|
|
|
name: "surname",
|
2023-07-31 12:03:03 +02:00
|
|
|
},
|
|
|
|
age: {
|
|
|
|
type: FieldType.NUMBER,
|
|
|
|
name: "age",
|
|
|
|
},
|
|
|
|
address: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "address",
|
|
|
|
},
|
|
|
|
jobTitle: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "jobTitle",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const randomRowData = () => ({
|
|
|
|
name: generator.first(),
|
|
|
|
surname: generator.last(),
|
|
|
|
age: generator.age(),
|
|
|
|
address: generator.address(),
|
|
|
|
jobTitle: generator.word(),
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("create", () => {
|
|
|
|
it("should persist a new row with only the provided view fields", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-07-31 12:03:03 +02:00
|
|
|
const view = await config.api.viewV2.create({
|
|
|
|
tableId: table._id!,
|
2023-08-01 12:08:40 +02:00
|
|
|
schema: {
|
2023-07-31 12:03:03 +02:00
|
|
|
name: { visible: true },
|
|
|
|
surname: { visible: true },
|
|
|
|
address: { visible: true },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const data = randomRowData()
|
2023-08-08 14:19:22 +02:00
|
|
|
const newRow = await config.api.row.save(view.id, {
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table!._id,
|
2023-07-31 12:03:03 +02:00
|
|
|
_viewId: view.id,
|
|
|
|
...data,
|
|
|
|
})
|
|
|
|
|
|
|
|
const row = await config.api.row.get(table._id!, newRow._id!)
|
|
|
|
expect(row.body).toEqual({
|
|
|
|
name: data.name,
|
|
|
|
surname: data.surname,
|
|
|
|
address: data.address,
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table!._id,
|
2023-09-08 15:29:05 +02:00
|
|
|
_id: newRow._id,
|
|
|
|
_rev: newRow._rev,
|
|
|
|
id: newRow.id,
|
|
|
|
...defaultRowFields,
|
2023-07-31 12:03:03 +02:00
|
|
|
})
|
|
|
|
expect(row.body._viewId).toBeUndefined()
|
|
|
|
expect(row.body.age).toBeUndefined()
|
|
|
|
expect(row.body.jobTitle).toBeUndefined()
|
|
|
|
})
|
|
|
|
})
|
2023-07-31 15:41:13 +02:00
|
|
|
|
|
|
|
describe("patch", () => {
|
|
|
|
it("should update only the view fields for a row", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-07-31 16:14:14 +02:00
|
|
|
const tableId = table._id!
|
2023-07-31 15:41:13 +02:00
|
|
|
const view = await config.api.viewV2.create({
|
|
|
|
tableId,
|
2023-08-01 12:08:40 +02:00
|
|
|
schema: {
|
2023-07-31 15:41:13 +02:00
|
|
|
name: { visible: true },
|
|
|
|
address: { visible: true },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2023-08-08 14:19:22 +02:00
|
|
|
const newRow = await config.api.row.save(view.id, {
|
2023-07-31 15:41:13 +02:00
|
|
|
tableId,
|
|
|
|
_viewId: view.id,
|
|
|
|
...randomRowData(),
|
|
|
|
})
|
|
|
|
const newData = randomRowData()
|
2023-08-08 14:19:22 +02:00
|
|
|
await config.api.row.patch(view.id, {
|
2023-07-31 15:41:13 +02:00
|
|
|
tableId,
|
|
|
|
_viewId: view.id,
|
|
|
|
_id: newRow._id!,
|
|
|
|
_rev: newRow._rev!,
|
|
|
|
...newData,
|
|
|
|
})
|
|
|
|
|
|
|
|
const row = await config.api.row.get(tableId, newRow._id!)
|
|
|
|
expect(row.body).toEqual({
|
|
|
|
...newRow,
|
|
|
|
name: newData.name,
|
|
|
|
address: newData.address,
|
2023-09-08 15:29:05 +02:00
|
|
|
_id: newRow._id,
|
2023-07-31 15:41:13 +02:00
|
|
|
_rev: expect.any(String),
|
2023-09-08 15:29:05 +02:00
|
|
|
id: newRow.id,
|
|
|
|
...defaultRowFields,
|
2023-07-31 15:41:13 +02:00
|
|
|
})
|
|
|
|
expect(row.body._viewId).toBeUndefined()
|
|
|
|
expect(row.body.age).toBeUndefined()
|
|
|
|
expect(row.body.jobTitle).toBeUndefined()
|
|
|
|
})
|
|
|
|
})
|
2023-07-31 16:14:14 +02:00
|
|
|
|
|
|
|
describe("destroy", () => {
|
|
|
|
it("should be able to delete a row", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-07-31 16:14:14 +02:00
|
|
|
const tableId = table._id!
|
|
|
|
const view = await config.api.viewV2.create({
|
|
|
|
tableId,
|
2023-08-01 12:08:40 +02:00
|
|
|
schema: {
|
2023-07-31 16:14:14 +02:00
|
|
|
name: { visible: true },
|
|
|
|
address: { visible: true },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
2023-09-08 15:46:10 +02:00
|
|
|
const createdRow = await createRow()
|
2023-07-31 16:14:14 +02:00
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-08-08 14:19:22 +02:00
|
|
|
await config.api.row.delete(view.id, [createdRow])
|
2023-07-31 16:14:14 +02:00
|
|
|
|
|
|
|
await assertRowUsage(rowUsage - 1)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
|
|
|
|
await config.api.row.get(tableId, createdRow._id!, {
|
|
|
|
expectStatus: 404,
|
|
|
|
})
|
|
|
|
})
|
2023-07-31 16:37:24 +02:00
|
|
|
|
|
|
|
it("should be able to delete multiple rows", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-07-31 16:37:24 +02:00
|
|
|
const tableId = table._id!
|
|
|
|
const view = await config.api.viewV2.create({
|
|
|
|
tableId,
|
2023-08-01 12:08:40 +02:00
|
|
|
schema: {
|
2023-07-31 16:37:24 +02:00
|
|
|
name: { visible: true },
|
|
|
|
address: { visible: true },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const rows = [
|
2023-09-08 15:46:10 +02:00
|
|
|
await createRow(tableId),
|
|
|
|
await createRow(tableId),
|
|
|
|
await createRow(tableId),
|
2023-07-31 16:37:24 +02:00
|
|
|
]
|
|
|
|
const rowUsage = await getRowUsage()
|
|
|
|
const queryUsage = await getQueryUsage()
|
|
|
|
|
2023-08-08 14:19:22 +02:00
|
|
|
await config.api.row.delete(view.id, [rows[0], rows[2]])
|
2023-07-31 16:37:24 +02:00
|
|
|
|
|
|
|
await assertRowUsage(rowUsage - 2)
|
|
|
|
await assertQueryUsage(queryUsage + 1)
|
|
|
|
|
|
|
|
await config.api.row.get(tableId, rows[0]._id!, {
|
|
|
|
expectStatus: 404,
|
|
|
|
})
|
|
|
|
await config.api.row.get(tableId, rows[2]._id!, {
|
|
|
|
expectStatus: 404,
|
|
|
|
})
|
|
|
|
await config.api.row.get(tableId, rows[1]._id!, { expectStatus: 200 })
|
|
|
|
})
|
2023-07-31 16:14:14 +02:00
|
|
|
})
|
2023-08-02 15:13:43 +02:00
|
|
|
|
|
|
|
describe("view search", () => {
|
2023-08-14 13:44:05 +02:00
|
|
|
const viewSchema = { age: { visible: true }, name: { visible: true } }
|
2023-09-08 15:54:27 +02:00
|
|
|
async function userTable(): Promise<Table> {
|
2023-08-02 15:13:43 +02:00
|
|
|
return {
|
2023-09-12 18:09:42 +02:00
|
|
|
name: `users_${generator.word()}`,
|
2023-09-08 15:54:27 +02:00
|
|
|
type: "table",
|
|
|
|
primary: ["id"],
|
2023-08-02 15:13:43 +02:00
|
|
|
schema: {
|
2023-09-08 15:54:27 +02:00
|
|
|
id: {
|
|
|
|
type: FieldType.AUTO,
|
|
|
|
name: "id",
|
|
|
|
autocolumn: true,
|
|
|
|
constraints: {
|
|
|
|
presence: true,
|
|
|
|
},
|
|
|
|
},
|
2023-08-02 15:13:43 +02:00
|
|
|
name: {
|
|
|
|
type: FieldType.STRING,
|
|
|
|
name: "name",
|
|
|
|
constraints: { type: "string" },
|
|
|
|
},
|
|
|
|
age: {
|
|
|
|
type: FieldType.NUMBER,
|
|
|
|
name: "age",
|
|
|
|
constraints: {},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-08 16:23:34 +02:00
|
|
|
it("returns empty rows from view when no schema is passed", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
const rows = []
|
|
|
|
for (let i = 0; i < 10; i++) {
|
2023-09-12 19:03:24 +02:00
|
|
|
rows.push(
|
|
|
|
await config.api.row.save(table._id!, { tableId: table._id })
|
|
|
|
)
|
2023-08-02 15:13:43 +02:00
|
|
|
}
|
|
|
|
|
2023-09-12 19:03:24 +02:00
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-08-02 15:13:43 +02:00
|
|
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(10)
|
|
|
|
expect(response.body).toEqual({
|
2023-09-08 16:23:34 +02:00
|
|
|
rows: expect.arrayContaining(
|
|
|
|
rows.map(r => ({
|
|
|
|
_viewId: createViewResponse.id,
|
|
|
|
tableId: table._id,
|
|
|
|
_id: r._id,
|
|
|
|
_rev: r._rev,
|
|
|
|
...defaultRowFields,
|
|
|
|
}))
|
|
|
|
),
|
|
|
|
hasNextPage: false,
|
|
|
|
bookmark: null,
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("searching respects the view filters", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
const expectedRows = []
|
|
|
|
for (let i = 0; i < 10; i++)
|
2023-09-12 19:03:24 +02:00
|
|
|
await config.api.row.save(table._id!, {
|
2023-08-02 15:13:43 +02:00
|
|
|
tableId: table._id,
|
|
|
|
name: generator.name(),
|
|
|
|
age: generator.integer({ min: 10, max: 30 }),
|
|
|
|
})
|
|
|
|
|
|
|
|
for (let i = 0; i < 5; i++)
|
|
|
|
expectedRows.push(
|
2023-09-12 19:03:24 +02:00
|
|
|
await config.api.row.save(table._id!, {
|
2023-08-02 15:13:43 +02:00
|
|
|
tableId: table._id,
|
|
|
|
name: generator.name(),
|
|
|
|
age: 40,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table._id!,
|
2023-08-07 14:33:50 +02:00
|
|
|
query: [{ operator: "equal", field: "age", value: 40 }],
|
2023-08-14 13:44:05 +02:00
|
|
|
schema: viewSchema,
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(5)
|
|
|
|
expect(response.body).toEqual({
|
|
|
|
rows: expect.arrayContaining(
|
2023-09-08 16:31:47 +02:00
|
|
|
expectedRows.map(r => ({
|
|
|
|
_viewId: createViewResponse.id,
|
|
|
|
tableId: table._id,
|
|
|
|
name: r.name,
|
|
|
|
age: r.age,
|
|
|
|
_id: r._id,
|
|
|
|
_rev: r._rev,
|
|
|
|
...defaultRowFields,
|
|
|
|
}))
|
2023-08-02 15:13:43 +02:00
|
|
|
),
|
2023-09-08 16:31:47 +02:00
|
|
|
hasNextPage: false,
|
|
|
|
bookmark: null,
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const sortTestOptions: [
|
|
|
|
{
|
|
|
|
field: string
|
|
|
|
order?: SortOrder
|
|
|
|
type?: SortType
|
|
|
|
},
|
|
|
|
string[]
|
|
|
|
][] = [
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "name",
|
|
|
|
order: SortOrder.ASCENDING,
|
|
|
|
type: SortType.STRING,
|
|
|
|
},
|
|
|
|
["Alice", "Bob", "Charly", "Danny"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "name",
|
|
|
|
},
|
|
|
|
["Alice", "Bob", "Charly", "Danny"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "name",
|
|
|
|
order: SortOrder.DESCENDING,
|
|
|
|
},
|
|
|
|
["Danny", "Charly", "Bob", "Alice"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "name",
|
|
|
|
order: SortOrder.DESCENDING,
|
|
|
|
type: SortType.STRING,
|
|
|
|
},
|
|
|
|
["Danny", "Charly", "Bob", "Alice"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "age",
|
|
|
|
order: SortOrder.ASCENDING,
|
|
|
|
type: SortType.number,
|
|
|
|
},
|
|
|
|
["Danny", "Alice", "Charly", "Bob"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "age",
|
|
|
|
order: SortOrder.ASCENDING,
|
|
|
|
},
|
|
|
|
["Danny", "Alice", "Charly", "Bob"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "age",
|
|
|
|
order: SortOrder.DESCENDING,
|
|
|
|
},
|
|
|
|
["Bob", "Charly", "Alice", "Danny"],
|
|
|
|
],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
field: "age",
|
|
|
|
order: SortOrder.DESCENDING,
|
|
|
|
type: SortType.number,
|
|
|
|
},
|
|
|
|
["Bob", "Charly", "Alice", "Danny"],
|
|
|
|
],
|
|
|
|
]
|
|
|
|
|
|
|
|
it.each(sortTestOptions)(
|
|
|
|
"allow sorting (%s)",
|
|
|
|
async (sortParams, expected) => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
const users = [
|
|
|
|
{ name: "Alice", age: 25 },
|
|
|
|
{ name: "Bob", age: 30 },
|
|
|
|
{ name: "Charly", age: 27 },
|
|
|
|
{ name: "Danny", age: 15 },
|
|
|
|
]
|
|
|
|
for (const user of users) {
|
2023-09-12 19:03:24 +02:00
|
|
|
await config.api.row.save(table._id!, {
|
|
|
|
tableId: table._id,
|
2023-08-02 15:13:43 +02:00
|
|
|
...user,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table._id,
|
2023-08-02 15:13:43 +02:00
|
|
|
sort: sortParams,
|
2023-08-14 13:44:05 +02:00
|
|
|
schema: viewSchema,
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(4)
|
2023-09-08 16:31:47 +02:00
|
|
|
expect(response.body.rows).toEqual(
|
|
|
|
expected.map(name => expect.objectContaining({ name }))
|
|
|
|
)
|
2023-08-02 15:13:43 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
it.each(sortTestOptions)(
|
|
|
|
"allow override the default view sorting (%s)",
|
|
|
|
async (sortParams, expected) => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
const users = [
|
|
|
|
{ name: "Alice", age: 25 },
|
|
|
|
{ name: "Bob", age: 30 },
|
|
|
|
{ name: "Charly", age: 27 },
|
|
|
|
{ name: "Danny", age: 15 },
|
|
|
|
]
|
|
|
|
for (const user of users) {
|
2023-09-12 19:03:24 +02:00
|
|
|
await config.api.row.save(table._id!, {
|
|
|
|
tableId: table._id,
|
2023-08-02 15:13:43 +02:00
|
|
|
...user,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table._id,
|
2023-08-02 15:13:43 +02:00
|
|
|
sort: {
|
|
|
|
field: "name",
|
|
|
|
order: SortOrder.ASCENDING,
|
|
|
|
type: SortType.STRING,
|
|
|
|
},
|
2023-08-14 13:44:05 +02:00
|
|
|
schema: viewSchema,
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const response = await config.api.viewV2.search(
|
|
|
|
createViewResponse.id,
|
|
|
|
{
|
2023-08-03 13:32:03 +02:00
|
|
|
sort: sortParams.field,
|
|
|
|
sortOrder: sortParams.order,
|
|
|
|
sortType: sortParams.type,
|
2023-08-25 16:28:29 +02:00
|
|
|
query: {},
|
2023-08-02 15:13:43 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(4)
|
2023-09-08 16:31:47 +02:00
|
|
|
expect(response.body.rows).toEqual(
|
|
|
|
expected.map(name => expect.objectContaining({ name }))
|
|
|
|
)
|
2023-08-02 15:13:43 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
it("when schema is defined, defined columns and row attributes are returned", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
const rows = []
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
rows.push(
|
2023-09-12 19:03:24 +02:00
|
|
|
await config.api.row.save(table._id!, {
|
2023-08-02 15:13:43 +02:00
|
|
|
tableId: table._id,
|
|
|
|
name: generator.name(),
|
|
|
|
age: generator.age(),
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const view = await config.api.viewV2.create({
|
2023-09-12 19:03:24 +02:00
|
|
|
tableId: table._id,
|
2023-08-14 13:44:05 +02:00
|
|
|
schema: { name: { visible: true } },
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
|
|
|
const response = await config.api.viewV2.search(view.id)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(10)
|
|
|
|
expect(response.body.rows).toEqual(
|
|
|
|
expect.arrayContaining(
|
|
|
|
rows.map(r => ({
|
2023-09-08 16:31:47 +02:00
|
|
|
...(isInternal
|
|
|
|
? expectAnyInternalColsAttributes
|
|
|
|
: expectAnyExternalColsAttributes),
|
2023-08-02 15:13:43 +02:00
|
|
|
_viewId: view.id,
|
|
|
|
name: r.name,
|
|
|
|
}))
|
|
|
|
)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("views without data can be returned", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-02 15:13:43 +02:00
|
|
|
|
2023-09-12 19:03:24 +02:00
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-08-02 15:13:43 +02:00
|
|
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(0)
|
|
|
|
})
|
2023-08-04 17:41:49 +02:00
|
|
|
|
2023-08-07 08:22:39 +02:00
|
|
|
it("respects the limit parameter", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-04 17:41:49 +02:00
|
|
|
const rows = []
|
|
|
|
for (let i = 0; i < 10; i++) {
|
2023-09-12 19:03:24 +02:00
|
|
|
rows.push(await createRow(table._id, { tableId: table._id }))
|
2023-08-04 17:41:49 +02:00
|
|
|
}
|
|
|
|
const limit = generator.integer({ min: 1, max: 8 })
|
|
|
|
|
2023-09-12 19:03:24 +02:00
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-08-04 17:41:49 +02:00
|
|
|
const response = await config.api.viewV2.search(createViewResponse.id, {
|
|
|
|
limit,
|
2023-08-25 16:28:29 +02:00
|
|
|
query: {},
|
2023-08-04 17:41:49 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(limit)
|
|
|
|
})
|
2023-08-07 09:49:04 +02:00
|
|
|
|
|
|
|
it("can handle pagination", async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-08-07 09:49:04 +02:00
|
|
|
const rows = []
|
|
|
|
for (let i = 0; i < 10; i++) {
|
2023-09-12 19:03:24 +02:00
|
|
|
rows.push(await createRow(table._id, { tableId: table._id }))
|
2023-08-07 09:49:04 +02:00
|
|
|
}
|
|
|
|
|
2023-09-12 19:03:24 +02:00
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-08-07 09:49:04 +02:00
|
|
|
const allRows = (await config.api.viewV2.search(createViewResponse.id))
|
|
|
|
.body.rows
|
|
|
|
|
|
|
|
const firstPageResponse = await config.api.viewV2.search(
|
|
|
|
createViewResponse.id,
|
|
|
|
{
|
|
|
|
paginate: true,
|
|
|
|
limit: 4,
|
2023-08-25 16:28:29 +02:00
|
|
|
query: {},
|
2023-08-07 09:49:04 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(firstPageResponse.body).toEqual({
|
|
|
|
rows: expect.arrayContaining(allRows.slice(0, 4)),
|
2023-09-08 16:39:13 +02:00
|
|
|
totalRows: isInternal ? 10 : undefined,
|
2023-08-07 09:49:04 +02:00
|
|
|
hasNextPage: true,
|
2023-09-08 16:39:13 +02:00
|
|
|
bookmark: expect.anything(),
|
2023-08-07 09:49:04 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const secondPageResponse = await config.api.viewV2.search(
|
|
|
|
createViewResponse.id,
|
|
|
|
{
|
|
|
|
paginate: true,
|
|
|
|
limit: 4,
|
|
|
|
bookmark: firstPageResponse.body.bookmark,
|
2023-08-25 16:28:29 +02:00
|
|
|
|
|
|
|
query: {},
|
2023-08-07 09:49:04 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(secondPageResponse.body).toEqual({
|
|
|
|
rows: expect.arrayContaining(allRows.slice(4, 8)),
|
2023-09-08 16:39:13 +02:00
|
|
|
totalRows: isInternal ? 10 : undefined,
|
2023-08-07 09:49:04 +02:00
|
|
|
hasNextPage: true,
|
2023-09-08 16:39:13 +02:00
|
|
|
bookmark: expect.anything(),
|
2023-08-07 09:49:04 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
const lastPageResponse = await config.api.viewV2.search(
|
|
|
|
createViewResponse.id,
|
|
|
|
{
|
|
|
|
paginate: true,
|
|
|
|
limit: 4,
|
|
|
|
bookmark: secondPageResponse.body.bookmark,
|
2023-08-25 16:28:29 +02:00
|
|
|
query: {},
|
2023-08-07 09:49:04 +02:00
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(lastPageResponse.body).toEqual({
|
|
|
|
rows: expect.arrayContaining(allRows.slice(8)),
|
2023-09-08 16:39:13 +02:00
|
|
|
totalRows: isInternal ? 10 : undefined,
|
2023-08-07 09:49:04 +02:00
|
|
|
hasNextPage: false,
|
2023-09-08 16:39:13 +02:00
|
|
|
bookmark: expect.anything(),
|
2023-08-07 09:49:04 +02:00
|
|
|
})
|
|
|
|
})
|
2023-09-04 14:12:37 +02:00
|
|
|
|
|
|
|
describe("permissions", () => {
|
|
|
|
let viewId: string
|
|
|
|
let tableId: string
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
2023-09-12 19:18:19 +02:00
|
|
|
const table = await config.createTable(await userTable())
|
2023-09-04 14:12:37 +02:00
|
|
|
const rows = []
|
|
|
|
for (let i = 0; i < 10; i++) {
|
2023-09-12 19:03:24 +02:00
|
|
|
rows.push(await createRow(table._id, { tableId: table._id }))
|
2023-09-04 14:12:37 +02:00
|
|
|
}
|
|
|
|
|
2023-09-12 19:03:24 +02:00
|
|
|
const createViewResponse = await config.api.viewV2.create({
|
|
|
|
tableId: table._id,
|
|
|
|
})
|
2023-09-04 14:12:37 +02:00
|
|
|
|
|
|
|
tableId = table._id!
|
|
|
|
viewId = createViewResponse.id
|
|
|
|
})
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
mocks.licenses.useViewPermissions()
|
|
|
|
})
|
|
|
|
|
|
|
|
it("does not allow public users to fetch by default", async () => {
|
|
|
|
await config.publish()
|
|
|
|
await config.api.viewV2.search(viewId, undefined, {
|
|
|
|
expectStatus: 403,
|
|
|
|
usePublicUser: true,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("allow public users to fetch when permissions are explicit", async () => {
|
|
|
|
await config.api.permission.set({
|
|
|
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
|
|
level: PermissionLevel.READ,
|
|
|
|
resourceId: viewId,
|
|
|
|
})
|
|
|
|
await config.publish()
|
|
|
|
|
|
|
|
const response = await config.api.viewV2.search(viewId, undefined, {
|
|
|
|
usePublicUser: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(10)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("allow public users to fetch when permissions are inherited", async () => {
|
|
|
|
await config.api.permission.set({
|
|
|
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
|
|
level: PermissionLevel.READ,
|
|
|
|
resourceId: tableId,
|
|
|
|
})
|
|
|
|
await config.publish()
|
|
|
|
|
|
|
|
const response = await config.api.viewV2.search(viewId, undefined, {
|
|
|
|
usePublicUser: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(response.body.rows).toHaveLength(10)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("respects inherited permissions, not allowing not public views from public tables", async () => {
|
|
|
|
await config.api.permission.set({
|
|
|
|
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
|
|
|
|
level: PermissionLevel.READ,
|
|
|
|
resourceId: tableId,
|
|
|
|
})
|
|
|
|
await config.api.permission.set({
|
|
|
|
roleId: roles.BUILTIN_ROLE_IDS.POWER,
|
|
|
|
level: PermissionLevel.READ,
|
|
|
|
resourceId: viewId,
|
|
|
|
})
|
|
|
|
await config.publish()
|
|
|
|
|
|
|
|
await config.api.viewV2.search(viewId, undefined, {
|
|
|
|
usePublicUser: true,
|
|
|
|
expectStatus: 403,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2023-08-02 15:13:43 +02:00
|
|
|
})
|
2023-07-31 12:03:03 +02:00
|
|
|
})
|
2021-09-30 13:55:21 +02:00
|
|
|
})
|