import { CartExpiryWorkerService } from './cart-expiry.worker'; import { tehranWallToUtc } from './cart-schedule'; describe('CartExpiryWorkerService', () => { function build(opts: { cfg?: Record; carts?: Array>; }) { const carts = opts.carts ?? []; const updates: Array<{ customerId: string; data: Record }> = []; const sms = { send: jest.fn(async () => ({ ok: true })) }; const prisma = { customerCart: { findMany: jest.fn(async () => carts), update: jest.fn(async ({ where, data }: { where: { customerId: string }; data: Record }) => { updates.push({ customerId: where.customerId, data }); return {}; }), }, $executeRaw: jest.fn(async () => 1), }; const cart = { getConfig: jest.fn(async () => ({ enabled: true, clearHour: 13, clearMinute: 0, sameDayDeferFromHour: 8, sameDayDeferFromMinute: 0, reminderEnabled: true, reminderHoursBefore: 2, reminderBody: 'تا %count ساعت', worker: 'on' as const, ...opts.cfg, })), }; const worker = new CartExpiryWorkerService(prisma as never, cart as never, sms as never); return { worker, sms, updates, prisma, cart }; } it('no-ops when disabled', async () => { const { worker, sms, prisma } = build({ cfg: { enabled: false } }); await worker.tick(tehranWallToUtc(2026, 11, 12, 12, 0, 0)); expect(sms.send).not.toHaveBeenCalled(); expect(prisma.customerCart.findMany).not.toHaveBeenCalled(); }); it('reminds carts due today (touched before 08:00), not deferred ones', async () => { const todayClear = tehranWallToUtc(2026, 11, 12, 13, 0, 0); const { worker, sms, updates } = build({ carts: [ { customerId: 'early', items: [{ pid: 'p1', vid: 'v1', qty: 1 }], reminderSentForClearAt: null, updatedAt: tehranWallToUtc(2026, 11, 12, 7, 0, 0), customer: { mobile: '09120000000', isActive: true }, }, { customerId: 'deferred', items: [{ pid: 'p2', vid: 'v2', qty: 1 }], reminderSentForClearAt: null, // 10:00 → deadline tomorrow 13 — no reminder yet at 11:30 today updatedAt: tehranWallToUtc(2026, 11, 12, 10, 0, 0), customer: { mobile: '09121111111', isActive: true }, }, ], }); await worker.tick(tehranWallToUtc(2026, 11, 12, 11, 30, 0)); expect(sms.send).toHaveBeenCalledTimes(1); expect(sms.send).toHaveBeenCalledWith('09120000000', expect.any(String)); expect(updates[0]?.customerId).toBe('early'); expect(updates[0]?.data.reminderSentForClearAt).toEqual(todayClear); }); it('does not clear 08–13 carts at today 13; clears pre-08 carts', async () => { const { worker, updates, sms } = build({ carts: [ { customerId: 'early', items: [{ pid: 'p1', vid: 'v1', qty: 1 }], reminderSentForClearAt: null, updatedAt: tehranWallToUtc(2026, 11, 12, 7, 0, 0), customer: { mobile: '09120000000', isActive: true }, }, { customerId: 'deferred', items: [{ pid: 'p2', vid: 'v2', qty: 1 }], reminderSentForClearAt: null, updatedAt: tehranWallToUtc(2026, 11, 12, 10, 0, 0), customer: { mobile: '09121111111', isActive: true }, }, { customerId: 'afternoon', items: [{ pid: 'p3', vid: 'v3', qty: 1 }], reminderSentForClearAt: null, updatedAt: tehranWallToUtc(2026, 11, 12, 14, 0, 0), customer: { mobile: '09122222222', isActive: true }, }, ], }); await worker.tick(tehranWallToUtc(2026, 11, 12, 13, 5, 0)); expect(sms.send).not.toHaveBeenCalled(); expect(updates).toHaveLength(1); expect(updates[0].customerId).toBe('early'); expect(updates[0].data.items).toEqual([]); }); it('clears deferred (08–13) carts at tomorrow 13', async () => { const { worker, updates } = build({ carts: [ { customerId: 'deferred', items: [{ pid: 'p2', vid: 'v2', qty: 1 }], reminderSentForClearAt: null, updatedAt: tehranWallToUtc(2026, 11, 12, 10, 0, 0), customer: { mobile: '09121111111', isActive: true }, }, ], }); await worker.tick(tehranWallToUtc(2026, 11, 13, 13, 5, 0)); expect(updates).toHaveLength(1); expect(updates[0].customerId).toBe('deferred'); }); });