123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
import { ClassReminderService } from '../class-reminder.service'
|
|
import { PrismaService } from '../../prisma/prisma.service'
|
|
import { SubscriptionMessageService } from '../../user/subscription-message.service'
|
|
import { ConfigService } from '@nestjs/config'
|
|
import { Logger } from '@nestjs/common'
|
|
import { BookingStatus } from '@mp-pilates/shared'
|
|
|
|
describe('ClassReminderService', () => {
|
|
const db = {
|
|
booking: { findMany: jest.fn(), updateMany: jest.fn(), update: jest.fn() },
|
|
studioConfig: { findFirst: jest.fn() },
|
|
}
|
|
const messages = {
|
|
getClassReminderTemplateId: jest.fn(),
|
|
sendClassReminderMessage: jest.fn(),
|
|
}
|
|
const config = { get: jest.fn() }
|
|
let service: ClassReminderService
|
|
|
|
beforeEach(() => {
|
|
jest.resetAllMocks()
|
|
messages.getClassReminderTemplateId.mockReturnValue('reminder-template-id')
|
|
db.studioConfig.findFirst.mockResolvedValue({ name: 'FocusCore Studio' })
|
|
service = new ClassReminderService(
|
|
db as unknown as PrismaService,
|
|
messages as unknown as SubscriptionMessageService,
|
|
config as unknown as ConfigService,
|
|
)
|
|
jest.spyOn(Logger.prototype, 'error').mockImplementation(() => {})
|
|
jest.spyOn(Logger.prototype, 'log').mockImplementation(() => {})
|
|
})
|
|
|
|
afterEach(() => jest.restoreAllMocks())
|
|
|
|
it('skips run when class reminder template is not configured', async () => {
|
|
messages.getClassReminderTemplateId.mockReturnValue('')
|
|
await service.run()
|
|
expect(db.booking.findMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('scans and sends reminders for bookings starting in ~1 hour (e.g. 60 mins)', async () => {
|
|
// Current time fixed or calculated
|
|
const now = new Date()
|
|
const targetDate = new Date(now.getTime() + 60 * 60 * 1000)
|
|
// Convert targetDate to China time string representation
|
|
// China is UTC+8
|
|
const chinaDate = new Date(targetDate.getTime() + 8 * 3600 * 1000)
|
|
const dateStr = chinaDate.toISOString().slice(0, 10)
|
|
const hours = String(chinaDate.getUTCHours()).padStart(2, '0')
|
|
const minutes = String(chinaDate.getUTCMinutes()).padStart(2, '0')
|
|
const startTime = `${hours}:${minutes}:00`
|
|
|
|
db.booking.findMany.mockResolvedValue([
|
|
{
|
|
id: 'booking-reminder-1',
|
|
userId: 'user-1',
|
|
status: BookingStatus.CONFIRMED,
|
|
user: { openid: 'openid-user-1' },
|
|
timeSlot: {
|
|
date: new Date(`${dateStr}T00:00:00.000Z`),
|
|
startTime,
|
|
endTime: '12:00:00',
|
|
},
|
|
},
|
|
])
|
|
db.booking.updateMany.mockResolvedValue({ count: 1 })
|
|
messages.sendClassReminderMessage.mockResolvedValue(true)
|
|
|
|
await service.run()
|
|
|
|
expect(db.booking.updateMany).toHaveBeenCalledWith({
|
|
where: {
|
|
id: 'booking-reminder-1',
|
|
status: BookingStatus.CONFIRMED,
|
|
classReminderClaimedAt: null,
|
|
},
|
|
data: { classReminderClaimedAt: expect.any(Date) },
|
|
})
|
|
|
|
expect(messages.sendClassReminderMessage).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
openid: 'openid-user-1',
|
|
userId: 'user-1',
|
|
bookingId: 'booking-reminder-1',
|
|
courseName: 'FocusCore Studio',
|
|
}),
|
|
)
|
|
|
|
expect(db.booking.update).toHaveBeenCalledWith({
|
|
where: { id: 'booking-reminder-1' },
|
|
data: { classReminderSentAt: expect.any(Date) },
|
|
})
|
|
})
|
|
|
|
it('skips bookings that are not in the 50-70 minutes window (e.g. starting in 2 hours)', async () => {
|
|
const now = new Date()
|
|
const targetDate = new Date(now.getTime() + 120 * 60 * 1000)
|
|
const chinaDate = new Date(targetDate.getTime() + 8 * 3600 * 1000)
|
|
const dateStr = chinaDate.toISOString().slice(0, 10)
|
|
const hours = String(chinaDate.getUTCHours()).padStart(2, '0')
|
|
const minutes = String(chinaDate.getUTCMinutes()).padStart(2, '0')
|
|
|
|
db.booking.findMany.mockResolvedValue([
|
|
{
|
|
id: 'booking-far',
|
|
userId: 'user-far',
|
|
status: BookingStatus.CONFIRMED,
|
|
user: { openid: 'openid-far' },
|
|
timeSlot: {
|
|
date: new Date(`${dateStr}T00:00:00.000Z`),
|
|
startTime: `${hours}:${minutes}:00`,
|
|
endTime: '20:00:00',
|
|
},
|
|
},
|
|
])
|
|
|
|
await service.run()
|
|
|
|
expect(db.booking.updateMany).not.toHaveBeenCalled()
|
|
expect(messages.sendClassReminderMessage).not.toHaveBeenCalled()
|
|
})
|
|
})
|