payload-plugin-scheduler
Version:
Scheduled posts plugin for PayloadCMS
280 lines (244 loc) • 7.34 kB
text/typescript
import { addMinutes } from 'date-fns'
import { type Payload } from 'payload'
import { INTERVAL } from '../src/payload.base.config'
const waitFor = (time: number): Promise<void> => new Promise(resolve => setTimeout(resolve, time))
describe('Plugin tests', () => {
const payload = globalThis.payloadClient as Payload
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const findSchedule = (id: string | number) =>
payload.find({
collection: 'scheduled_posts',
where: {
and: [
{
'post.value': {
equals: id,
},
},
{
'post.relationTo': {
equals: 'posts',
},
},
],
},
})
it('schedules collection docs', async () => {
const pubDate = addMinutes(new Date(), 2).toISOString()
const doc = await payload.create({
collection: 'posts',
data: {
title: 'hello',
content: 'world',
publish_date: pubDate,
},
})
expect(doc.id).toBeTruthy()
expect(doc.publish_date).toBe(pubDate)
const {
totalDocs,
docs: [schedule],
} = await findSchedule(doc.id)
expect(totalDocs).toBe(1)
expect(schedule.date).toBe(doc.publish_date)
expect(schedule.status).toBe('queued')
})
it('schedules global docs', async () => {
const pubDate = addMinutes(new Date(), 2).toISOString()
const doc = await payload.updateGlobal({
slug: 'home',
data: {
title: 'hello world',
publish_date: pubDate,
_status: 'draft',
},
})
expect(doc.publish_date).toBe(pubDate)
expect(doc._status).toBe('draft')
const {
totalDocs,
docs: [schedule],
} = await payload.find({
collection: 'scheduled_posts',
where: {
global: {
equals: 'home'
}
}
})
expect(totalDocs).toBe(1)
expect(schedule.date).toBe(doc.publish_date)
expect(schedule.status).toBe('queued')
})
it('bounds publish_date', async () => {
const now = new Date()
const futureDate = addMinutes(now, 10)
const doc = await payload.create({
collection: 'posts',
data: {
title: 'hello',
content: 'world',
publish_date: futureDate.toISOString(),
_status: 'published',
},
})
// if `publish_date` is in the future at publish time, should be reset to now
const actualPublishDate = new Date(doc.publish_date as string)
expect(actualPublishDate.getMinutes()).toBe(now.getMinutes())
// shouldn't be scheduled either
const { totalDocs } = await findSchedule(doc.id)
expect(totalDocs).toBe(0)
})
it(
'publishes scheduled posts',
async () => {
const now = new Date()
const pubDate = addMinutes(now, INTERVAL)
const draft = await payload.create({
collection: 'posts',
data: {
title: 'hello',
publish_date: pubDate.toISOString(),
},
})
const { totalDocs } = await findSchedule(draft.id)
expect(totalDocs).toBe(1)
// wait for the interval + extra
await waitFor(1000 * 60 * INTERVAL + 5000)
const publishedDraft = await payload.findByID({
collection: 'posts',
id: draft.id,
})
// eslint-disable-next-line no-underscore-dangle
expect(publishedDraft._status).toBe('published')
const { totalDocs: updatedTotalDocs } = await findSchedule(draft.id)
expect(updatedTotalDocs).toBe(0)
},
// timeout = interval + 1 min
1000 * 60 * (INTERVAL + 1),
)
it('handles subsequent draft updates to pending posts', async () => {
const now = new Date()
const pubDate = addMinutes(now, 5)
const draft = await payload.create({
collection: 'posts',
data: {
title: 'hello world',
publish_date: pubDate.toISOString(),
},
})
expect(draft.id).toBeTruthy()
const { totalDocs, docs } = await findSchedule(draft.id)
expect(totalDocs).toBe(1)
expect(docs[0].date).toBe(pubDate.toISOString())
const updatedPubDate = addMinutes(pubDate, 5).toISOString()
await payload.update({
collection: 'posts',
data: {
publish_date: updatedPubDate,
},
where: {
id: {
equals: draft.id,
},
},
})
const updatedSchedules = await findSchedule(draft.id)
expect(updatedSchedules.totalDocs).toBe(1)
expect(updatedSchedules.docs[0].date).toBe(updatedPubDate)
})
it('cancels pending schedules', async () => {
const now = new Date()
const pubDate = addMinutes(now, 5)
const draft = await payload.create({
collection: 'posts',
data: {
title: 'hello world',
publish_date: pubDate.toISOString(),
},
})
const { totalDocs, docs } = await findSchedule(draft.id)
expect(totalDocs).toBe(1)
expect(docs[0].date).toBe(pubDate.toISOString())
const updated = await payload.update({
collection: 'posts',
where: {
id: {
equals: draft.id,
},
},
data: {
// @ts-expect-error
publish_date: null,
},
})
expect(updated.docs[0].publish_date).toBeNull()
const updatedSchedule = await findSchedule(draft.id)
expect(updatedSchedule.totalDocs).toBe(0)
})
it('handles delete docs', async () => {
const now = new Date()
const pubDate = addMinutes(now, 5)
const draft = await payload.create({
collection: 'posts',
data: {
title: 'hello world',
publish_date: pubDate.toISOString(),
},
})
const { totalDocs } = await findSchedule(draft.id)
expect(totalDocs).toBe(1)
payload.delete({
collection: 'posts',
id: draft.id,
})
await waitFor(2000)
const updatedSchedules = await findSchedule(draft.id)
expect(updatedSchedules.totalDocs).toBe(0)
}, 9000)
it('handles peer hook errors', async () => {
const now = new Date()
const pubDate = addMinutes(now, INTERVAL)
const [draft, badDraft, badDraft2] = await Promise.all([
payload.create({
collection: 'pageswithextrahooks',
data: {
title: 'hello world',
publish_date: pubDate.toISOString(),
}
}),
payload.create({
collection: 'pageswithextrahooks',
data: {
title: 'throw',
publish_date: pubDate.toISOString(),
}
}),
payload.create({
collection: 'pageswithextrahooks',
data: {
title: 'commit-and-throw',
publish_date: pubDate.toISOString(),
}
}),
])
await waitFor(1000 * 60 * INTERVAL + 2000)
const [updatedDraft, updatedBadDraft, updatedBadDraft2] = await Promise.all([
payload.findByID({
collection: 'pageswithextrahooks',
id: draft.id,
}),
payload.findByID({
collection: 'pageswithextrahooks',
id: badDraft.id,
}),
payload.findByID({
collection: 'pageswithextrahooks',
id: badDraft2.id,
}),
])
expect(updatedDraft._status).toBe('published')
expect(updatedBadDraft._status).toBe('draft')
expect(updatedBadDraft2._status).toBe('published')
}, 1000 * 60 * INTERVAL + 5000)
})