2025-02-04 16:14:58 +01:00
|
|
|
import { createAutomationBuilder } from "../utilities/AutomationTestBuilder"
|
|
|
|
import TestConfiguration from "../../../tests/utilities/TestConfiguration"
|
2025-02-17 18:39:38 +01:00
|
|
|
import {
|
2025-02-18 11:48:16 +01:00
|
|
|
captureAutomationMessages,
|
|
|
|
captureAutomationRemovals,
|
2025-02-17 18:39:38 +01:00
|
|
|
captureAutomationResults,
|
2025-02-18 11:48:16 +01:00
|
|
|
triggerCron,
|
2025-02-17 18:39:38 +01:00
|
|
|
} from "../utilities"
|
|
|
|
import { automations } from "@budibase/pro"
|
2025-02-18 11:48:16 +01:00
|
|
|
import { AutomationData, AutomationStatus } from "@budibase/types"
|
|
|
|
import { MAX_AUTOMATION_RECURRING_ERRORS } from "../../../constants"
|
2025-02-18 15:01:32 +01:00
|
|
|
import { queue } from "@budibase/backend-core"
|
2025-02-04 16:14:58 +01:00
|
|
|
|
|
|
|
describe("cron trigger", () => {
|
|
|
|
const config = new TestConfiguration()
|
|
|
|
|
2025-02-05 18:19:11 +01:00
|
|
|
beforeAll(async () => {
|
2025-02-04 16:14:58 +01:00
|
|
|
await config.init()
|
2025-03-03 18:22:09 +01:00
|
|
|
await config.api.automation.deleteAll()
|
2025-02-04 16:14:58 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
afterAll(() => {
|
|
|
|
config.end()
|
|
|
|
})
|
|
|
|
|
2025-02-17 18:39:38 +01:00
|
|
|
beforeEach(async () => {
|
|
|
|
const { automations } = await config.api.automation.fetch()
|
|
|
|
for (const automation of automations) {
|
|
|
|
await config.api.automation.delete(automation)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2025-02-05 10:22:37 +01:00
|
|
|
it("should queue a Bull cron job", async () => {
|
2025-02-10 17:15:53 +01:00
|
|
|
const { automation } = await createAutomationBuilder(config)
|
2025-02-06 16:47:10 +01:00
|
|
|
.onCron({ cron: "* * * * *" })
|
2025-02-04 16:14:58 +01:00
|
|
|
.serverLog({
|
|
|
|
text: "Hello, world!",
|
|
|
|
})
|
|
|
|
.save()
|
|
|
|
|
2025-02-18 11:48:16 +01:00
|
|
|
const messages = await captureAutomationMessages(automation, () =>
|
2025-02-10 14:06:13 +01:00
|
|
|
config.api.application.publish()
|
|
|
|
)
|
2025-02-17 18:39:38 +01:00
|
|
|
expect(messages).toHaveLength(1)
|
2025-02-05 10:22:37 +01:00
|
|
|
|
2025-02-17 18:39:38 +01:00
|
|
|
const repeat = messages[0].opts?.repeat
|
2025-02-05 10:22:37 +01:00
|
|
|
if (!repeat || !("cron" in repeat)) {
|
|
|
|
throw new Error("Expected cron repeat")
|
|
|
|
}
|
|
|
|
expect(repeat.cron).toEqual("* * * * *")
|
2025-02-04 16:14:58 +01:00
|
|
|
})
|
2025-02-05 16:24:54 +01:00
|
|
|
|
|
|
|
it("should fail if the cron expression is invalid", async () => {
|
2025-02-05 18:39:38 +01:00
|
|
|
await createAutomationBuilder(config)
|
2025-02-06 16:47:10 +01:00
|
|
|
.onCron({ cron: "* * * * * *" })
|
2025-02-05 16:24:54 +01:00
|
|
|
.serverLog({
|
|
|
|
text: "Hello, world!",
|
|
|
|
})
|
|
|
|
.save()
|
|
|
|
|
|
|
|
await config.api.application.publish(config.getAppId(), {
|
|
|
|
status: 500,
|
|
|
|
body: {
|
|
|
|
message:
|
|
|
|
'Deployment Failed: Invalid automation CRON "* * * * * *" - Expected 5 values, but got 6.',
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
2025-02-17 18:39:38 +01:00
|
|
|
|
2025-02-18 12:59:14 +01:00
|
|
|
it("should stop if the job fails more than N times", async () => {
|
2025-02-18 11:48:16 +01:00
|
|
|
const { automation } = await createAutomationBuilder(config)
|
2025-02-17 18:39:38 +01:00
|
|
|
.onCron({ cron: "* * * * *" })
|
|
|
|
.queryRows({
|
|
|
|
// @ts-expect-error intentionally sending invalid data
|
|
|
|
tableId: null,
|
|
|
|
})
|
|
|
|
.save()
|
|
|
|
|
2025-02-18 11:48:16 +01:00
|
|
|
const [message] = await captureAutomationMessages(automation, () =>
|
|
|
|
config.api.application.publish()
|
2025-02-17 18:39:38 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
await config.withProdApp(async () => {
|
2025-02-18 15:01:32 +01:00
|
|
|
let results: queue.TestQueueMessage<AutomationData>[] = []
|
2025-02-18 11:48:16 +01:00
|
|
|
const removed = await captureAutomationRemovals(automation, async () => {
|
|
|
|
results = await captureAutomationResults(automation, async () => {
|
|
|
|
for (let i = 0; i < MAX_AUTOMATION_RECURRING_ERRORS; i++) {
|
|
|
|
triggerCron(message)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
expect(removed).toHaveLength(1)
|
|
|
|
expect(removed[0].id).toEqual(message.id)
|
|
|
|
|
|
|
|
expect(results).toHaveLength(5)
|
|
|
|
|
|
|
|
const search = await automations.logs.logSearch({
|
|
|
|
automationId: automation._id,
|
2025-02-18 12:47:18 +01:00
|
|
|
status: AutomationStatus.STOPPED_ERROR,
|
2025-02-17 18:39:38 +01:00
|
|
|
})
|
2025-02-18 11:48:16 +01:00
|
|
|
expect(search.data).toHaveLength(1)
|
|
|
|
expect(search.data[0].status).toEqual(AutomationStatus.STOPPED_ERROR)
|
2025-02-17 18:39:38 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should fill in the timestamp if one is not provided", async () => {
|
|
|
|
const runner = await createAutomationBuilder(config)
|
|
|
|
.onCron({ cron: "* * * * *" })
|
|
|
|
.serverLog({
|
|
|
|
text: "Hello, world!",
|
|
|
|
})
|
|
|
|
.save()
|
|
|
|
|
|
|
|
await config.api.application.publish()
|
|
|
|
|
|
|
|
const results = await captureAutomationResults(
|
|
|
|
runner.automation,
|
|
|
|
async () => {
|
|
|
|
await runner.trigger({ timeout: 1000, fields: {} })
|
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(results).toHaveLength(1)
|
|
|
|
expect(results[0].data.event.timestamp).toBeWithin(
|
|
|
|
Date.now() - 1000,
|
|
|
|
Date.now() + 1000
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("should use the given timestamp if one is given", async () => {
|
|
|
|
const timestamp = 1234
|
|
|
|
const runner = await createAutomationBuilder(config)
|
|
|
|
.onCron({ cron: "* * * * *" })
|
|
|
|
.serverLog({
|
|
|
|
text: "Hello, world!",
|
|
|
|
})
|
|
|
|
.save()
|
|
|
|
|
|
|
|
await config.api.application.publish()
|
|
|
|
|
|
|
|
const results = await captureAutomationResults(
|
|
|
|
runner.automation,
|
|
|
|
async () => {
|
|
|
|
await runner.trigger({ timeout: 1000, fields: {}, timestamp })
|
|
|
|
}
|
|
|
|
)
|
|
|
|
expect(results).toHaveLength(1)
|
|
|
|
expect(results[0].data.event.timestamp).toEqual(timestamp)
|
|
|
|
})
|
2025-02-04 16:14:58 +01:00
|
|
|
})
|