feat: 优化微信订阅消息体系并新增约课取消通知与开课前1小时提醒
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user