Get viewV2.spec.ts running against real databases.

This commit is contained in:
Sam Rose 2024-03-14 17:11:09 +00:00
parent 7a7a30b8f5
commit 850fb3d4ec
No known key found for this signature in database
6 changed files with 256 additions and 199 deletions

View File

@ -16,7 +16,7 @@ import {
ViewV2, ViewV2,
} from "@budibase/types" } from "@budibase/types"
import * as setup from "./utilities" import * as setup from "./utilities"
import { mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
const { basicRow } = setup.structures const { basicRow } = setup.structures
const { BUILTIN_ROLE_IDS } = roles const { BUILTIN_ROLE_IDS } = roles
@ -44,7 +44,10 @@ describe("/permission", () => {
table = (await config.createTable()) as typeof table table = (await config.createTable()) as typeof table
row = await config.createRow() row = await config.createRow()
view = await config.api.viewV2.create({ tableId: table._id }) view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
})
perms = await config.api.permission.add({ perms = await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: table._id, resourceId: table._id,

View File

@ -42,6 +42,7 @@ tk.freeze(timestamp)
jest.unmock("mysql2") jest.unmock("mysql2")
jest.unmock("mysql2/promise") jest.unmock("mysql2/promise")
jest.unmock("mssql") jest.unmock("mssql")
jest.unmock("pg")
describe.each([ describe.each([
["internal", undefined], ["internal", undefined],
@ -152,8 +153,8 @@ describe.each([
table = await config.api.table.save(defaultTable()) table = await config.api.table.save(defaultTable())
}) })
describe("save, load, update", () => { describe("create", () => {
it("returns a success message when the row is created", async () => { it("creates a new row successfully", async () => {
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const row = await config.api.row.save(table._id!, { const row = await config.api.row.save(table._id!, {
name: "Test Contact", name: "Test Contact",
@ -163,7 +164,44 @@ describe.each([
await assertRowUsage(rowUsage + 1) await assertRowUsage(rowUsage + 1)
}) })
it("Increment row autoId per create row request", async () => { it("fails to create a row for a table that does not exist", async () => {
const rowUsage = await getRowUsage()
await config.api.row.save("1234567", {}, { status: 404 })
await assertRowUsage(rowUsage)
})
it("fails to create a row if required fields are missing", async () => {
const rowUsage = await getRowUsage()
const table = await config.api.table.save(
saveTableRequest({
schema: {
required: {
type: FieldType.STRING,
name: "required",
constraints: {
type: "string",
presence: true,
},
},
},
})
)
await config.api.row.save(
table._id!,
{},
{
status: 500,
body: {
validationErrors: {
required: ["can't be blank"],
},
},
}
)
await assertRowUsage(rowUsage)
})
it("increment row autoId per create row request", async () => {
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const newTable = await config.api.table.save( const newTable = await config.api.table.save(
@ -198,52 +236,6 @@ describe.each([
await assertRowUsage(rowUsage + 10) await assertRowUsage(rowUsage + 10)
}) })
it("updates a row successfully", async () => {
const existing = await config.api.row.save(table._id!, {})
const rowUsage = await getRowUsage()
const res = await config.api.row.save(table._id!, {
_id: existing._id,
_rev: existing._rev,
name: "Updated Name",
})
expect(res.name).toEqual("Updated Name")
await assertRowUsage(rowUsage)
})
it("should load a row", async () => {
const existing = await config.api.row.save(table._id!, {})
const res = await config.api.row.get(table._id!, existing._id!)
expect(res).toEqual({
...existing,
...defaultRowFields,
})
})
it("should list all rows for given tableId", async () => {
const table = await config.api.table.save(defaultTable())
const rows = await Promise.all([
config.api.row.save(table._id!, {}),
config.api.row.save(table._id!, {}),
])
const res = await config.api.row.fetch(table._id!)
expect(res.map(r => r._id)).toEqual(
expect.arrayContaining(rows.map(r => r._id))
)
})
it("load should return 404 when row does not exist", async () => {
const table = await config.api.table.save(defaultTable())
await config.api.row.save(table._id!, {})
await config.api.row.get(table._id!, "1234567", {
status: 404,
})
})
isInternal && isInternal &&
it("row values are coerced", async () => { it("row values are coerced", async () => {
const str: FieldSchema = { const str: FieldSchema = {
@ -296,8 +288,6 @@ describe.each([
} }
const table = await config.api.table.save( const table = await config.api.table.save(
saveTableRequest({ saveTableRequest({
name: "TestTable2",
type: "table",
schema: { schema: {
name: str, name: str,
stringUndefined: str, stringUndefined: str,
@ -404,53 +394,60 @@ describe.each([
}) })
}) })
describe("view save", () => { describe("get", () => {
it("views have extra data trimmed", async () => { it("reads an existing row successfully", async () => {
const table = await config.api.table.save( const existing = await config.api.row.save(table._id!, {})
saveTableRequest({
name: "orders",
schema: {
Country: {
type: FieldType.STRING,
name: "Country",
},
Story: {
type: FieldType.STRING,
name: "Story",
},
},
})
)
const createViewResponse = await config.api.viewV2.create({ const res = await config.api.row.get(table._id!, existing._id!)
tableId: table._id!,
name: uuid.v4(),
schema: {
Country: {
visible: true,
},
},
})
const createRowResponse = await config.api.row.save( expect(res).toEqual({
createViewResponse.id, ...existing,
{
Country: "Aussy",
Story: "aaaaa",
}
)
const row = await config.api.row.get(table._id!, createRowResponse._id!)
expect(row.Story).toBeUndefined()
expect(row).toEqual({
...defaultRowFields, ...defaultRowFields,
Country: "Aussy",
id: createRowResponse.id,
_id: createRowResponse._id,
_rev: createRowResponse._rev,
tableId: table._id,
}) })
}) })
it("returns 404 when row does not exist", async () => {
const table = await config.api.table.save(defaultTable())
await config.api.row.save(table._id!, {})
await config.api.row.get(table._id!, "1234567", {
status: 404,
})
})
})
describe("fetch", () => {
it("fetches all rows for given tableId", async () => {
const table = await config.api.table.save(defaultTable())
const rows = await Promise.all([
config.api.row.save(table._id!, {}),
config.api.row.save(table._id!, {}),
])
const res = await config.api.row.fetch(table._id!)
expect(res.map(r => r._id)).toEqual(
expect.arrayContaining(rows.map(r => r._id))
)
})
it("returns 404 when table does not exist", async () => {
await config.api.row.fetch("1234567", { status: 404 })
})
})
describe("update", () => {
it("updates an existing row successfully", async () => {
const existing = await config.api.row.save(table._id!, {})
const rowUsage = await getRowUsage()
const res = await config.api.row.save(table._id!, {
_id: existing._id,
_rev: existing._rev,
name: "Updated Name",
})
expect(res.name).toEqual("Updated Name")
await assertRowUsage(rowUsage)
})
}) })
describe("patch", () => { describe("patch", () => {
@ -947,6 +944,7 @@ describe.each([
const table = await config.api.table.save(await userTable()) const table = await config.api.table.save(await userTable())
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
schema: { schema: {
name: { visible: true }, name: { visible: true },
surname: { visible: true }, surname: { visible: true },
@ -984,6 +982,7 @@ describe.each([
const tableId = table._id! const tableId = table._id!
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: tableId, tableId: tableId,
name: generator.guid(),
schema: { schema: {
name: { visible: true }, name: { visible: true },
address: { visible: true }, address: { visible: true },
@ -1026,6 +1025,7 @@ describe.each([
const tableId = table._id! const tableId = table._id!
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: tableId, tableId: tableId,
name: generator.guid(),
schema: { schema: {
name: { visible: true }, name: { visible: true },
address: { visible: true }, address: { visible: true },
@ -1049,6 +1049,7 @@ describe.each([
const tableId = table._id! const tableId = table._id!
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: tableId, tableId: tableId,
name: generator.guid(),
schema: { schema: {
name: { visible: true }, name: { visible: true },
address: { visible: true }, address: { visible: true },
@ -1109,6 +1110,7 @@ describe.each([
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
}) })
const response = await config.api.viewV2.search(createViewResponse.id) const response = await config.api.viewV2.search(createViewResponse.id)
@ -1155,6 +1157,7 @@ describe.each([
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
query: [ query: [
{ operator: SearchQueryOperators.EQUAL, field: "age", value: 40 }, { operator: SearchQueryOperators.EQUAL, field: "age", value: 40 },
], ],
@ -1279,6 +1282,7 @@ describe.each([
async (sortParams, expected) => { async (sortParams, expected) => {
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
sort: sortParams, sort: sortParams,
schema: viewSchema, schema: viewSchema,
}) })
@ -1299,6 +1303,7 @@ describe.each([
async (sortParams, expected) => { async (sortParams, expected) => {
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
sort: { sort: {
field: "name", field: "name",
order: SortOrder.ASCENDING, order: SortOrder.ASCENDING,
@ -1339,6 +1344,7 @@ describe.each([
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
schema: { name: { visible: true } }, schema: { name: { visible: true } },
}) })
const response = await config.api.viewV2.search(view.id) const response = await config.api.viewV2.search(view.id)
@ -1361,6 +1367,7 @@ describe.each([
const table = await config.api.table.save(await userTable()) const table = await config.api.table.save(await userTable())
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
}) })
const response = await config.api.viewV2.search(createViewResponse.id) const response = await config.api.viewV2.search(createViewResponse.id)
expect(response.rows).toHaveLength(0) expect(response.rows).toHaveLength(0)
@ -1376,6 +1383,7 @@ describe.each([
const createViewResponse = await config.api.viewV2.create({ const createViewResponse = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
}) })
const response = await config.api.viewV2.search(createViewResponse.id, { const response = await config.api.viewV2.search(createViewResponse.id, {
limit, limit,
@ -1392,6 +1400,7 @@ describe.each([
) )
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
}) })
const rows = (await config.api.viewV2.search(view.id)).rows const rows = (await config.api.viewV2.search(view.id)).rows
@ -1466,6 +1475,7 @@ describe.each([
view = await config.api.viewV2.create({ view = await config.api.viewV2.create({
tableId: table._id!, tableId: table._id!,
name: generator.guid(),
}) })
}) })

View File

@ -20,7 +20,7 @@ import sdk from "../../../sdk"
import * as uuid from "uuid" import * as uuid from "uuid"
import tk from "timekeeper" import tk from "timekeeper"
import { mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
import { TableToBuild } from "../../../tests/utilities/TestConfiguration" import { TableToBuild } from "../../../tests/utilities/TestConfiguration"
tk.freeze(mocks.date.MOCK_DATE) tk.freeze(mocks.date.MOCK_DATE)
@ -417,8 +417,8 @@ describe("/tables", () => {
it("should fetch views", async () => { it("should fetch views", async () => {
const tableId = config.table!._id! const tableId = config.table!._id!
const views = [ const views = [
await config.api.viewV2.create({ tableId }), await config.api.viewV2.create({ tableId, name: generator.guid() }),
await config.api.viewV2.create({ tableId }), await config.api.viewV2.create({ tableId, name: generator.guid() }),
] ]
const res = await request const res = await request
@ -455,7 +455,7 @@ describe("/tables", () => {
}, },
})) }))
await config.api.viewV2.create({ tableId }) await config.api.viewV2.create({ tableId, name: generator.guid() })
await config.createLegacyView() await config.createLegacyView()
const res = await config.api.table.fetch() const res = await config.api.table.fetch()

View File

@ -1,9 +1,11 @@
import * as setup from "./utilities" import * as setup from "./utilities"
import { import {
CreateViewRequest, CreateViewRequest,
Datasource,
FieldSchema, FieldSchema,
FieldType, FieldType,
INTERNAL_TABLE_SOURCE_ID, INTERNAL_TABLE_SOURCE_ID,
SaveTableRequest,
SearchQueryOperators, SearchQueryOperators,
SortOrder, SortOrder,
SortType, SortType,
@ -14,65 +16,88 @@ import {
ViewV2, ViewV2,
} from "@budibase/types" } from "@budibase/types"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
import { generateDatasourceID } from "../../../db/utils" import * as uuid from "uuid"
import { databaseTestProviders } from "../../../integrations/tests/utils"
import merge from "lodash/merge"
function priceTable(): Table { jest.unmock("mysql2")
return { jest.unmock("mysql2/promise")
name: "table", jest.unmock("mssql")
type: "table", jest.unmock("pg")
sourceId: INTERNAL_TABLE_SOURCE_ID,
sourceType: TableSourceType.INTERNAL,
schema: {
Price: {
type: FieldType.NUMBER,
name: "Price",
constraints: {},
},
Category: {
type: FieldType.STRING,
name: "Category",
constraints: {
type: "string",
},
},
},
}
}
const config = setup.getConfig()
beforeAll(async () => {
await config.init()
})
describe.each([ describe.each([
["internal ds", () => config.createTable(priceTable())], ["internal", undefined],
[ ["postgres", databaseTestProviders.postgres],
"external ds", ["mysql", databaseTestProviders.mysql],
async () => { ["mssql", databaseTestProviders.mssql],
const datasource = await config.createDatasource({ ["mariadb", databaseTestProviders.mariadb],
datasource: { ])("/v2/views (%s)", (_, dsProvider) => {
...setup.structures.basicDatasource().datasource, const config = setup.getConfig()
plus: true,
_id: generateDatasourceID({ plus: true }),
},
})
return config.createExternalTable({
...priceTable(),
sourceId: datasource._id,
sourceType: TableSourceType.EXTERNAL,
})
},
],
])("/v2/views (%s)", (_, tableBuilder) => {
let table: Table let table: Table
let datasource: Datasource
function saveTableRequest(
...overrides: Partial<SaveTableRequest>[]
): SaveTableRequest {
const req: SaveTableRequest = {
name: uuid.v4().substring(0, 16),
type: "table",
sourceType: datasource
? TableSourceType.EXTERNAL
: TableSourceType.INTERNAL,
sourceId: datasource ? datasource._id! : INTERNAL_TABLE_SOURCE_ID,
primary: ["id"],
schema: {
id: {
type: FieldType.AUTO,
name: "id",
autocolumn: true,
constraints: {
presence: true,
},
},
},
}
return merge(req, ...overrides)
}
function priceTable(): SaveTableRequest {
return saveTableRequest({
schema: {
Price: {
type: FieldType.NUMBER,
name: "Price",
constraints: {},
},
Category: {
type: FieldType.STRING,
name: "Category",
constraints: {
type: "string",
},
},
},
})
}
beforeAll(async () => { beforeAll(async () => {
table = await tableBuilder() await config.init()
if (dsProvider) {
datasource = await config.createDatasource({
datasource: await dsProvider.datasource(),
})
}
table = await config.api.table.save(priceTable())
}) })
afterAll(setup.afterAll) afterAll(async () => {
if (dsProvider) {
await dsProvider.stop()
}
setup.afterAll()
})
describe("create", () => { describe("create", () => {
it("persist the view when the view is successfully created", async () => { it("persist the view when the view is successfully created", async () => {
@ -186,9 +211,12 @@ describe.each([
let view: ViewV2 let view: ViewV2
beforeEach(async () => { beforeEach(async () => {
table = await tableBuilder() table = await config.api.table.save(priceTable())
view = await config.api.viewV2.create({ name: "View A" }) view = await config.api.viewV2.create({
tableId: table._id!,
name: "View A",
})
}) })
it("can update an existing view data", async () => { it("can update an existing view data", async () => {
@ -247,6 +275,9 @@ describe.each([
...updatedData, ...updatedData,
schema: { schema: {
...table.schema, ...table.schema,
id: expect.objectContaining({
visible: false,
}),
Category: expect.objectContaining({ Category: expect.objectContaining({
visible: false, visible: false,
}), }),
@ -320,23 +351,27 @@ describe.each([
}) })
it("cannot update views v1", async () => { it("cannot update views v1", async () => {
const viewV1 = await config.createLegacyView() const viewV1 = await config.api.legacyView.save({
await config.api.viewV2.update( tableId: table._id!,
{ name: generator.guid(),
...viewV1, filters: [],
}, schema: {},
{ })
await config.api.viewV2.update(viewV1 as unknown as ViewV2, {
status: 400,
body: {
message: "Only views V2 can be updated",
status: 400, status: 400,
body: { },
message: "Only views V2 can be updated", })
status: 400,
},
}
)
}) })
it("cannot update the a view with unmatching ids between url and body", async () => { it("cannot update the a view with unmatching ids between url and body", async () => {
const anotherView = await config.api.viewV2.create() const anotherView = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
})
const result = await config const result = await config
.request!.put(`/api/v2/views/${anotherView.id}`) .request!.put(`/api/v2/views/${anotherView.id}`)
.send(view) .send(view)
@ -411,7 +446,10 @@ describe.each([
let view: ViewV2 let view: ViewV2
beforeAll(async () => { beforeAll(async () => {
view = await config.api.viewV2.create() view = await config.api.viewV2.create({
tableId: table._id!,
name: generator.guid(),
})
}) })
it("can delete an existing view", async () => { it("can delete an existing view", async () => {
@ -448,4 +486,43 @@ describe.each([
expect(viewSchema.Price?.visible).toEqual(false) expect(viewSchema.Price?.visible).toEqual(false)
}) })
}) })
describe("read", () => {
it("views have extra data trimmed", async () => {
const table = await config.api.table.save(
saveTableRequest({
name: "orders",
schema: {
Country: {
type: FieldType.STRING,
name: "Country",
},
Story: {
type: FieldType.STRING,
name: "Story",
},
},
})
)
const view = await config.api.viewV2.create({
tableId: table._id!,
name: uuid.v4(),
schema: {
Country: {
visible: true,
},
},
})
let row = await config.api.row.save(view.id, {
Country: "Aussy",
Story: "aaaaa",
})
row = await config.api.row.get(table._id!, row._id!)
expect(row.Story).toBeUndefined()
expect(row.Country).toEqual("Aussy")
})
})
}) })

View File

@ -1,21 +0,0 @@
import * as search from "../../app/rows/search"
describe("removeEmptyFilters", () => {
it("0 should not be removed", () => {
const filters = search.removeEmptyFilters({
equal: {
column: 0,
},
})
expect((filters.equal as any).column).toBe(0)
})
it("empty string should be removed", () => {
const filters = search.removeEmptyFilters({
equal: {
column: "",
},
})
expect(Object.values(filters.equal as any).length).toBe(0)
})
})

View File

@ -11,21 +11,9 @@ import sdk from "../../../sdk"
export class ViewV2API extends TestAPI { export class ViewV2API extends TestAPI {
create = async ( create = async (
viewData?: Partial<CreateViewRequest>, view: CreateViewRequest,
expectations?: Expectations expectations?: Expectations
): Promise<ViewV2> => { ): Promise<ViewV2> => {
let tableId = viewData?.tableId
if (!tableId && !this.config.table) {
throw "Test requires table to be configured."
}
tableId = tableId || this.config.table!._id!
const view = {
tableId,
name: generator.guid(),
...viewData,
}
const exp: Expectations = { const exp: Expectations = {
status: 201, status: 201,
...expectations, ...expectations,