270 lines
7.9 KiB
TypeScript
270 lines
7.9 KiB
TypeScript
import { AttachmentCleanup } from "../attachments"
|
|
import { FieldType, Table, Row, TableSourceType } from "@budibase/types"
|
|
import { DEFAULT_BB_DATASOURCE_ID } from "../../../constants"
|
|
import { objectStore, db, context } from "@budibase/backend-core"
|
|
import * as uuid from "uuid"
|
|
|
|
const BUCKET = "prod-budi-app-assets"
|
|
const FILE_NAME = "file/thing.jpg"
|
|
const DEV_APPID = "abc_dev_123"
|
|
const PROD_APPID = "abc_123"
|
|
|
|
jest.mock("@budibase/backend-core", () => {
|
|
const actual = jest.requireActual("@budibase/backend-core")
|
|
return {
|
|
...actual,
|
|
context: {
|
|
...actual.context,
|
|
getAppId: jest.fn(),
|
|
},
|
|
objectStore: {
|
|
deleteFiles: jest.fn(),
|
|
ObjectStoreBuckets: actual.objectStore.ObjectStoreBuckets,
|
|
},
|
|
db: {
|
|
isProdAppID: jest.fn(),
|
|
getProdAppID: jest.fn(),
|
|
dbExists: jest.fn(),
|
|
},
|
|
}
|
|
})
|
|
|
|
const mockedDeleteFiles = objectStore.deleteFiles as jest.MockedFunction<
|
|
typeof objectStore.deleteFiles
|
|
>
|
|
|
|
const rowGenerators: [
|
|
string,
|
|
(
|
|
| FieldType.ATTACHMENT_SINGLE
|
|
| FieldType.ATTACHMENTS
|
|
| FieldType.SIGNATURE_SINGLE
|
|
),
|
|
string,
|
|
(fileKey?: string) => Row
|
|
][] = [
|
|
[
|
|
"row with a attachment list column",
|
|
FieldType.ATTACHMENTS,
|
|
"attach",
|
|
function rowWithAttachments(fileKey: string = FILE_NAME): Row {
|
|
return {
|
|
attach: [
|
|
{
|
|
size: 1,
|
|
extension: "jpg",
|
|
key: fileKey,
|
|
},
|
|
],
|
|
}
|
|
},
|
|
],
|
|
[
|
|
"row with a single attachment column",
|
|
FieldType.ATTACHMENT_SINGLE,
|
|
"attach",
|
|
function rowWithAttachments(fileKey: string = FILE_NAME): Row {
|
|
return {
|
|
attach: {
|
|
size: 1,
|
|
extension: "jpg",
|
|
key: fileKey,
|
|
},
|
|
}
|
|
},
|
|
],
|
|
[
|
|
"row with a single signature column",
|
|
FieldType.SIGNATURE_SINGLE,
|
|
"signature",
|
|
function rowWithSignature(): Row {
|
|
return {
|
|
signature: {
|
|
size: 1,
|
|
extension: "png",
|
|
key: `${uuid.v4()}.png`,
|
|
},
|
|
}
|
|
},
|
|
],
|
|
]
|
|
|
|
describe.each(rowGenerators)(
|
|
"attachment cleanup",
|
|
(_, attachmentFieldType, colKey, rowGenerator) => {
|
|
function tableGenerator(): Table {
|
|
return {
|
|
name: "table",
|
|
sourceId: DEFAULT_BB_DATASOURCE_ID,
|
|
sourceType: TableSourceType.INTERNAL,
|
|
type: "table",
|
|
schema: {
|
|
attach: {
|
|
name: "attach",
|
|
type: attachmentFieldType,
|
|
constraints: {},
|
|
},
|
|
signature: {
|
|
name: "signature",
|
|
type: FieldType.SIGNATURE_SINGLE,
|
|
constraints: {},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
const getRowKeys = (row: any, col: string) => {
|
|
return Array.isArray(row[col])
|
|
? row[col].map((entry: any) => entry.key)
|
|
: [row[col]?.key]
|
|
}
|
|
|
|
beforeEach(() => {
|
|
mockedDeleteFiles.mockClear()
|
|
jest.resetAllMocks()
|
|
|
|
jest.spyOn(context, "getAppId").mockReturnValue(DEV_APPID)
|
|
jest.spyOn(db, "isProdAppID").mockReturnValue(false)
|
|
jest.spyOn(db, "getProdAppID").mockReturnValue(PROD_APPID)
|
|
jest.spyOn(db, "dbExists").mockReturnValue(Promise.resolve(false))
|
|
})
|
|
|
|
// Ignore calls to prune attachments when app is in production.
|
|
it(`${attachmentFieldType} - should not attempt to delete attachments/signatures if a published app exists`, async () => {
|
|
jest.spyOn(db, "dbExists").mockReturnValue(Promise.resolve(true))
|
|
const originalTable = tableGenerator()
|
|
delete originalTable.schema[colKey]
|
|
await AttachmentCleanup.tableUpdate(originalTable, [rowGenerator()], {
|
|
oldTable: tableGenerator(),
|
|
})
|
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should be able to cleanup a table update`, async () => {
|
|
const originalTable = tableGenerator()
|
|
delete originalTable.schema[colKey]
|
|
const targetRow = rowGenerator()
|
|
|
|
await AttachmentCleanup.tableUpdate(originalTable, [targetRow], {
|
|
oldTable: tableGenerator(),
|
|
})
|
|
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(
|
|
BUCKET,
|
|
getRowKeys(targetRow, colKey)
|
|
)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should be able to cleanup a table deletion`, async () => {
|
|
const targetRow = rowGenerator()
|
|
await AttachmentCleanup.tableDelete(tableGenerator(), [targetRow])
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(
|
|
BUCKET,
|
|
getRowKeys(targetRow, colKey)
|
|
)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should handle table column renaming`, async () => {
|
|
const updatedTable = tableGenerator()
|
|
updatedTable.schema.col2 = updatedTable.schema[colKey]
|
|
delete updatedTable.schema.attach
|
|
await AttachmentCleanup.tableUpdate(updatedTable, [rowGenerator()], {
|
|
oldTable: tableGenerator(),
|
|
rename: { old: colKey, updated: "col2" },
|
|
})
|
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it(`${attachmentFieldType} - shouldn't cleanup if no table changes`, async () => {
|
|
await AttachmentCleanup.tableUpdate(tableGenerator(), [rowGenerator()], {
|
|
oldTable: tableGenerator(),
|
|
})
|
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should handle row updates`, async () => {
|
|
const updatedRow = rowGenerator()
|
|
delete updatedRow[colKey]
|
|
|
|
const targetRow = rowGenerator()
|
|
await AttachmentCleanup.rowUpdate(tableGenerator(), {
|
|
row: updatedRow,
|
|
oldRow: targetRow,
|
|
})
|
|
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(
|
|
BUCKET,
|
|
getRowKeys(targetRow, colKey)
|
|
)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should handle row deletion`, async () => {
|
|
const targetRow = rowGenerator()
|
|
await AttachmentCleanup.rowDelete(tableGenerator(), [targetRow])
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(
|
|
BUCKET,
|
|
getRowKeys(targetRow, colKey)
|
|
)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should handle row deletion, prune signature`, async () => {
|
|
const targetRow = rowGenerator()
|
|
await AttachmentCleanup.rowDelete(tableGenerator(), [targetRow])
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(
|
|
BUCKET,
|
|
getRowKeys(targetRow, colKey)
|
|
)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should handle row deletion and not throw when attachments are undefined`, async () => {
|
|
await AttachmentCleanup.rowDelete(tableGenerator(), [
|
|
{
|
|
[colKey]: undefined,
|
|
},
|
|
])
|
|
})
|
|
|
|
it(`${attachmentFieldType} - shouldn't cleanup attachments if row not updated`, async () => {
|
|
const targetRow = rowGenerator()
|
|
await AttachmentCleanup.rowUpdate(tableGenerator(), {
|
|
row: targetRow,
|
|
oldRow: targetRow,
|
|
})
|
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should be able to cleanup a column and not throw when attachments are undefined`, async () => {
|
|
const originalTable = tableGenerator()
|
|
delete originalTable.schema[colKey]
|
|
const row1 = rowGenerator("file 1")
|
|
const row2 = rowGenerator("file 2")
|
|
await AttachmentCleanup.tableUpdate(
|
|
originalTable,
|
|
[row1, { [colKey]: undefined }, row2],
|
|
{
|
|
oldTable: tableGenerator(),
|
|
}
|
|
)
|
|
const expectedKeys = [row1, row2].reduce((acc: string[], row) => {
|
|
acc = [...acc, ...getRowKeys(row, colKey)]
|
|
return acc
|
|
}, [])
|
|
expect(mockedDeleteFiles).toHaveBeenCalledTimes(1)
|
|
expect(mockedDeleteFiles).toHaveBeenCalledWith(BUCKET, expectedKeys)
|
|
})
|
|
|
|
it(`${attachmentFieldType} - should be able to cleanup a column and not throw when ALL attachments are undefined`, async () => {
|
|
const originalTable = tableGenerator()
|
|
delete originalTable.schema[colKey]
|
|
await AttachmentCleanup.tableUpdate(
|
|
originalTable,
|
|
[{}, { attach: undefined }],
|
|
{
|
|
oldTable: tableGenerator(),
|
|
}
|
|
)
|
|
expect(mockedDeleteFiles).not.toHaveBeenCalled()
|
|
})
|
|
}
|
|
)
|