2024-08-09 11:30:27 +02:00
|
|
|
import { setEnv as setCoreEnv } from "@budibase/backend-core"
|
2024-08-01 12:44:58 +02:00
|
|
|
import nock from "nock"
|
2023-02-27 17:25:48 +01:00
|
|
|
|
|
|
|
import TestConfiguration from "../../tests/utilities/TestConfiguration"
|
2024-09-04 15:21:25 +02:00
|
|
|
import {
|
|
|
|
Datasource,
|
|
|
|
FieldType,
|
|
|
|
SourceName,
|
2024-09-06 18:29:56 +02:00
|
|
|
Table,
|
2024-09-04 15:21:25 +02:00
|
|
|
TableSourceType,
|
|
|
|
} from "@budibase/types"
|
2024-09-06 16:03:17 +02:00
|
|
|
import { GoogleSheetsMock } from "./utils/googlesheets"
|
2023-02-27 17:25:48 +01:00
|
|
|
|
|
|
|
describe("Google Sheets Integration", () => {
|
2024-09-04 15:21:25 +02:00
|
|
|
const config = new TestConfiguration()
|
|
|
|
|
2023-10-30 17:46:27 +01:00
|
|
|
let cleanupEnv: () => void
|
2024-09-04 15:21:25 +02:00
|
|
|
let datasource: Datasource
|
2024-09-06 16:03:17 +02:00
|
|
|
let mock: GoogleSheetsMock
|
2024-09-04 15:21:25 +02:00
|
|
|
|
|
|
|
beforeAll(async () => {
|
2024-08-09 11:30:27 +02:00
|
|
|
cleanupEnv = setCoreEnv({
|
2023-10-30 17:46:27 +01:00
|
|
|
GOOGLE_CLIENT_ID: "test",
|
|
|
|
GOOGLE_CLIENT_SECRET: "test",
|
|
|
|
})
|
2024-09-04 15:21:25 +02:00
|
|
|
|
2024-09-06 16:03:17 +02:00
|
|
|
await config.init()
|
|
|
|
|
2024-09-04 15:21:25 +02:00
|
|
|
datasource = await config.api.datasource.create({
|
|
|
|
name: "Test Datasource",
|
|
|
|
type: "datasource",
|
|
|
|
source: SourceName.GOOGLE_SHEETS,
|
2024-09-06 16:03:17 +02:00
|
|
|
config: {
|
|
|
|
spreadsheetId: "randomId",
|
|
|
|
auth: {
|
|
|
|
appId: "appId",
|
|
|
|
accessToken: "accessToken",
|
|
|
|
refreshToken: "refreshToken",
|
|
|
|
},
|
|
|
|
},
|
2024-09-04 15:21:25 +02:00
|
|
|
})
|
2023-03-06 11:33:49 +01:00
|
|
|
})
|
|
|
|
|
2023-03-27 20:38:49 +02:00
|
|
|
afterAll(async () => {
|
2023-10-30 17:46:27 +01:00
|
|
|
cleanupEnv()
|
|
|
|
config.end()
|
2023-03-27 20:38:49 +02:00
|
|
|
})
|
|
|
|
|
2023-02-27 17:25:48 +01:00
|
|
|
beforeEach(async () => {
|
2024-08-01 12:44:58 +02:00
|
|
|
nock.cleanAll()
|
2024-09-06 16:03:17 +02:00
|
|
|
mock = GoogleSheetsMock.forDatasource(datasource)
|
2024-08-01 12:44:58 +02:00
|
|
|
})
|
2024-08-01 12:43:37 +02:00
|
|
|
|
2024-09-06 16:03:17 +02:00
|
|
|
describe("create", () => {
|
|
|
|
it("creates a new table", async () => {
|
2024-09-10 15:39:23 +02:00
|
|
|
const table = await config.api.table.save({
|
2024-09-06 16:03:17 +02:00
|
|
|
name: "Test Table",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
name: {
|
|
|
|
name: "name",
|
2023-02-27 17:25:48 +01:00
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
2024-09-06 16:03:17 +02:00
|
|
|
},
|
|
|
|
description: {
|
|
|
|
name: "description",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-05-23 10:22:26 +02:00
|
|
|
})
|
2024-09-06 17:55:16 +02:00
|
|
|
|
2024-09-10 15:39:23 +02:00
|
|
|
expect(table.name).toEqual("Test Table")
|
|
|
|
|
2024-09-09 17:33:35 +02:00
|
|
|
expect(mock.cell("A1")).toEqual("name")
|
|
|
|
expect(mock.cell("B1")).toEqual("description")
|
|
|
|
expect(mock.cell("A2")).toEqual(null)
|
|
|
|
expect(mock.cell("B2")).toEqual(null)
|
2023-05-23 10:22:26 +02:00
|
|
|
})
|
2024-09-10 15:39:23 +02:00
|
|
|
|
|
|
|
it("can handle multiple tables", async () => {
|
|
|
|
const table1 = await config.api.table.save({
|
|
|
|
name: "Test Table 1",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
one: {
|
|
|
|
name: "one",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
const table2 = await config.api.table.save({
|
|
|
|
name: "Test Table 2",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
two: {
|
|
|
|
name: "two",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(table1.name).toEqual("Test Table 1")
|
|
|
|
expect(table2.name).toEqual("Test Table 2")
|
|
|
|
|
|
|
|
expect(mock.cell("Test Table 1!A1")).toEqual("one")
|
|
|
|
expect(mock.cell("Test Table 1!A2")).toEqual(null)
|
|
|
|
expect(mock.cell("Test Table 2!A1")).toEqual("two")
|
|
|
|
expect(mock.cell("Test Table 2!A2")).toEqual(null)
|
|
|
|
})
|
2023-05-23 10:22:26 +02:00
|
|
|
})
|
2024-09-06 18:29:56 +02:00
|
|
|
|
2024-09-10 17:58:33 +02:00
|
|
|
describe("read", () => {
|
|
|
|
let table: Table
|
|
|
|
beforeEach(async () => {
|
|
|
|
table = await config.api.table.save({
|
|
|
|
name: "Test Table",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
name: {
|
|
|
|
name: "name",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
name: "description",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
await config.api.row.bulkImport(table._id!, {
|
|
|
|
rows: [
|
|
|
|
{
|
|
|
|
name: "Test Contact 1",
|
|
|
|
description: "original description 1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Test Contact 2",
|
|
|
|
description: "original description 2",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("can read table details", async () => {
|
|
|
|
const response = await config.api.table.get(table._id!)
|
|
|
|
expect(response.name).toEqual("Test Table")
|
|
|
|
expect(response.schema).toEqual({
|
|
|
|
name: {
|
|
|
|
name: "name",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
name: "description",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("can read table rows", async () => {
|
|
|
|
const rows = await config.api.row.fetch(table._id!)
|
|
|
|
expect(rows.length).toEqual(2)
|
|
|
|
expect(rows[0].name).toEqual("Test Contact 1")
|
|
|
|
expect(rows[0].description).toEqual("original description 1")
|
|
|
|
expect(rows[0]._id).toEqual("%5B2%5D")
|
|
|
|
expect(rows[1].name).toEqual("Test Contact 2")
|
|
|
|
expect(rows[1].description).toEqual("original description 2")
|
|
|
|
expect(rows[1]._id).toEqual("%5B3%5D")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("can get a specific row", async () => {
|
|
|
|
const row1 = await config.api.row.get(table._id!, "2")
|
|
|
|
expect(row1.name).toEqual("Test Contact 1")
|
|
|
|
expect(row1.description).toEqual("original description 1")
|
|
|
|
|
|
|
|
const row2 = await config.api.row.get(table._id!, "3")
|
|
|
|
expect(row2.name).toEqual("Test Contact 2")
|
|
|
|
expect(row2.description).toEqual("original description 2")
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-09-06 18:29:56 +02:00
|
|
|
describe("update", () => {
|
|
|
|
let table: Table
|
|
|
|
beforeEach(async () => {
|
|
|
|
table = await config.api.table.save({
|
|
|
|
name: "Test Table",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
name: {
|
|
|
|
name: "name",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
name: "description",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-09-09 18:51:32 +02:00
|
|
|
it("should be able to add a new row", async () => {
|
|
|
|
const row = await config.api.row.save(table._id!, {
|
|
|
|
name: "Test Contact",
|
|
|
|
description: "original description",
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(row.name).toEqual("Test Contact")
|
|
|
|
expect(row.description).toEqual("original description")
|
|
|
|
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description")
|
|
|
|
|
|
|
|
const row2 = await config.api.row.save(table._id!, {
|
|
|
|
name: "Test Contact 2",
|
|
|
|
description: "original description 2",
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(row2.name).toEqual("Test Contact 2")
|
|
|
|
expect(row2.description).toEqual("original description 2")
|
|
|
|
|
|
|
|
// Notable that adding a new row adds it at the top, not the bottom. Not
|
|
|
|
// entirely sure if this is the intended behaviour or an incorrect
|
|
|
|
// implementation of the GoogleSheetsMock.
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact 2")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description 2")
|
|
|
|
|
|
|
|
expect(mock.cell("A3")).toEqual("Test Contact")
|
|
|
|
expect(mock.cell("B3")).toEqual("original description")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should be able to add multiple rows", async () => {
|
|
|
|
await config.api.row.bulkImport(table._id!, {
|
|
|
|
rows: [
|
|
|
|
{
|
|
|
|
name: "Test Contact 1",
|
|
|
|
description: "original description 1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Test Contact 2",
|
|
|
|
description: "original description 2",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact 1")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description 1")
|
|
|
|
expect(mock.cell("A3")).toEqual("Test Contact 2")
|
|
|
|
expect(mock.cell("B3")).toEqual("original description 2")
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should be able to update a row", async () => {
|
|
|
|
const row = await config.api.row.save(table._id!, {
|
2024-09-06 18:29:56 +02:00
|
|
|
name: "Test Contact",
|
|
|
|
description: "original description",
|
|
|
|
})
|
2024-09-09 18:51:32 +02:00
|
|
|
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description")
|
|
|
|
|
|
|
|
await config.api.row.save(table._id!, {
|
|
|
|
...row,
|
|
|
|
name: "Test Contact Updated",
|
|
|
|
description: "original description updated",
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact Updated")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description updated")
|
2024-09-06 18:29:56 +02:00
|
|
|
})
|
2024-09-10 18:41:33 +02:00
|
|
|
|
|
|
|
it("should be able to rename a column", async () => {
|
|
|
|
const row = await config.api.row.save(table._id!, {
|
|
|
|
name: "Test Contact",
|
|
|
|
description: "original description",
|
|
|
|
})
|
|
|
|
|
|
|
|
const { name, ...otherColumns } = table.schema
|
|
|
|
const renamedTable = await config.api.table.save({
|
|
|
|
...table,
|
|
|
|
schema: {
|
|
|
|
...otherColumns,
|
|
|
|
renamed: {
|
|
|
|
...table.schema.name,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
_rename: {
|
|
|
|
old: "name",
|
|
|
|
updated: "renamed",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(renamedTable.schema.name).not.toBeDefined()
|
|
|
|
expect(renamedTable.schema.renamed).toBeDefined()
|
|
|
|
|
|
|
|
expect(mock.cell("A1")).toEqual("renamed")
|
|
|
|
expect(mock.cell("B1")).toEqual("description")
|
|
|
|
expect(mock.cell("A2")).toEqual("Test Contact")
|
|
|
|
expect(mock.cell("B2")).toEqual("original description")
|
|
|
|
expect(mock.cell("A3")).toEqual(null)
|
|
|
|
expect(mock.cell("B3")).toEqual(null)
|
|
|
|
|
|
|
|
const renamedRow = await config.api.row.get(table._id!, row._id!)
|
|
|
|
expect(renamedRow.renamed).toEqual("Test Contact")
|
|
|
|
expect(renamedRow.description).toEqual("original description")
|
|
|
|
expect(renamedRow.name).not.toBeDefined()
|
|
|
|
})
|
2024-09-10 19:29:11 +02:00
|
|
|
|
|
|
|
// TODO: this gets the error "Sheet is not large enough to fit 27 columns. Resize the sheet first."
|
|
|
|
// eslint-disable-next-line jest/no-commented-out-tests
|
|
|
|
// it("should be able to add a new column", async () => {
|
|
|
|
// const updatedTable = await config.api.table.save({
|
|
|
|
// ...table,
|
|
|
|
// schema: {
|
|
|
|
// ...table.schema,
|
|
|
|
// newColumn: {
|
|
|
|
// name: "newColumn",
|
|
|
|
// type: FieldType.STRING,
|
|
|
|
// },
|
|
|
|
// },
|
|
|
|
// })
|
|
|
|
|
|
|
|
// expect(updatedTable.schema.newColumn).toBeDefined()
|
|
|
|
|
|
|
|
// expect(mock.cell("A1")).toEqual("name")
|
|
|
|
// expect(mock.cell("B1")).toEqual("description")
|
|
|
|
// expect(mock.cell("C1")).toEqual("newColumn")
|
|
|
|
// })
|
|
|
|
|
|
|
|
it("should be able to delete a column", async () => {
|
|
|
|
const row = await config.api.row.save(table._id!, {
|
|
|
|
name: "Test Contact",
|
|
|
|
description: "original description",
|
|
|
|
})
|
|
|
|
|
|
|
|
const updatedTable = await config.api.table.save({
|
|
|
|
...table,
|
|
|
|
schema: {
|
|
|
|
name: {
|
|
|
|
name: "name",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(updatedTable.schema.name).toBeDefined()
|
|
|
|
expect(updatedTable.schema.description).not.toBeDefined()
|
|
|
|
|
|
|
|
// TODO: we don't delete data in deleted columns yet, should we?
|
|
|
|
// expect(mock.cell("A1")).toEqual("name")
|
|
|
|
// expect(mock.cell("B1")).toEqual(null)
|
|
|
|
|
|
|
|
const updatedRow = await config.api.row.get(table._id!, row._id!)
|
|
|
|
expect(updatedRow.name).toEqual("Test Contact")
|
|
|
|
expect(updatedRow.description).not.toBeDefined()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe("delete", () => {
|
|
|
|
let table: Table
|
|
|
|
beforeEach(async () => {
|
|
|
|
table = await config.api.table.save({
|
|
|
|
name: "Test Table",
|
|
|
|
type: "table",
|
|
|
|
sourceId: datasource._id!,
|
|
|
|
sourceType: TableSourceType.EXTERNAL,
|
|
|
|
schema: {
|
|
|
|
name: {
|
|
|
|
name: "name",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
description: {
|
|
|
|
name: "description",
|
|
|
|
type: FieldType.STRING,
|
|
|
|
constraints: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
await config.api.row.bulkImport(table._id!, {
|
|
|
|
rows: [
|
|
|
|
{
|
|
|
|
name: "Test Contact 1",
|
|
|
|
description: "original description 1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Test Contact 2",
|
|
|
|
description: "original description 2",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2024-09-16 12:59:06 +02:00
|
|
|
it("can delete a table", async () => {
|
2024-09-16 13:06:27 +02:00
|
|
|
expect(mock.sheet(table.name)).toBeDefined()
|
2024-09-10 19:29:11 +02:00
|
|
|
await config.api.table.destroy(table._id!, table._rev!)
|
2024-09-16 12:59:06 +02:00
|
|
|
expect(mock.sheet(table.name)).toBeUndefined()
|
2024-09-10 19:29:11 +02:00
|
|
|
})
|
|
|
|
|
2024-09-16 13:06:27 +02:00
|
|
|
it("can delete a row", async () => {
|
2024-09-10 19:29:11 +02:00
|
|
|
const rows = await config.api.row.fetch(table._id!)
|
|
|
|
expect(rows.length).toEqual(2)
|
|
|
|
|
2024-09-16 13:06:27 +02:00
|
|
|
// Because row IDs in Google Sheets are sequential and determined by the
|
|
|
|
// actual row in the sheet, deleting a row will shift the row IDs down by
|
|
|
|
// one. This is why we reverse the rows before deleting them.
|
|
|
|
for (const row of rows.reverse()) {
|
2024-09-10 19:29:11 +02:00
|
|
|
await config.api.row.delete(table._id!, { _id: row._id! })
|
|
|
|
}
|
|
|
|
|
|
|
|
expect(mock.cell("A1")).toEqual("name")
|
|
|
|
expect(mock.cell("B1")).toEqual("description")
|
|
|
|
expect(mock.cell("A2")).toEqual(null)
|
|
|
|
expect(mock.cell("B2")).toEqual(null)
|
|
|
|
expect(mock.cell("A3")).toEqual(null)
|
|
|
|
expect(mock.cell("B3")).toEqual(null)
|
|
|
|
|
|
|
|
const emptyRows = await config.api.row.fetch(table._id!)
|
|
|
|
expect(emptyRows.length).toEqual(0)
|
|
|
|
})
|
2024-09-06 18:29:56 +02:00
|
|
|
})
|
2023-02-27 17:25:48 +01:00
|
|
|
})
|