diff --git a/src/queues/email-send.queue.test.ts b/src/queues/email-send.queue.test.ts index c89b519..a056847 100644 --- a/src/queues/email-send.queue.test.ts +++ b/src/queues/email-send.queue.test.ts @@ -3,19 +3,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' const mockAdd = vi.hoisted(() => vi.fn().mockResolvedValue({ id: 'job-1' })) vi.mock('bullmq', () => ({ - Queue: vi.fn().mockImplementation(() => ({ - add: mockAdd, - })), + Queue: vi.fn().mockImplementation(() => ({ add: mockAdd })), })) -import { enqueueEmailSend, type EmailSendJobData, resetQueueForTesting } from './email-send.queue' -import { Queue } from 'bullmq' +import { enqueueEmailSend, emailSendQueue, type EmailSendJobData } from './email-send.queue' describe('enqueueEmailSend', () => { - beforeEach(() => { - vi.clearAllMocks() - resetQueueForTesting() - }) + beforeEach(() => vi.clearAllMocks()) it('enqueued Job mit korrekten Daten', async () => { const data: EmailSendJobData = { @@ -24,7 +18,7 @@ describe('enqueueEmailSend', () => { recipientEmail: 'empfaenger@example.com', recipientHash: 'abc123hash', } - const result = await enqueueEmailSend(data) + const result = await enqueueEmailSend(data, emailSendQueue) expect(result.ok).toBe(true) expect(mockAdd).toHaveBeenCalledWith('send', data, expect.objectContaining({ attempts: 3 })) }) @@ -36,7 +30,8 @@ describe('enqueueEmailSend', () => { recipientEmail: 'x@example.com', recipientHash: 'hash1', } - const result = await enqueueEmailSend(data) + const result = await enqueueEmailSend(data, emailSendQueue) + expect(result.ok).toBe(true) if (result.ok) expect(result.data).toBe('job-1') }) @@ -44,7 +39,7 @@ describe('enqueueEmailSend', () => { mockAdd.mockRejectedValueOnce(new Error('Redis down')) const result = await enqueueEmailSend({ tenantId: 't1', campaignId: 'c1', recipientEmail: 'x@x.com', recipientHash: 'h1', - }) + }, emailSendQueue) expect(result.ok).toBe(false) if (!result.ok) expect(result.error.message).toBe('Redis down') }) @@ -52,19 +47,20 @@ describe('enqueueEmailSend', () => { it('nutzt exponentielles Backoff', async () => { await enqueueEmailSend({ tenantId: 't1', campaignId: 'c1', recipientEmail: 'x@x.com', recipientHash: 'h1', - }) + }, emailSendQueue) expect(mockAdd).toHaveBeenCalledWith( 'send', expect.anything(), - expect.objectContaining({ - backoff: expect.objectContaining({ type: 'exponential' }), - }) + expect.objectContaining({ backoff: expect.objectContaining({ type: 'exponential' }) }) ) }) - it('Queue wird mit Queue-Name "email:send" initialisiert', async () => { - await enqueueEmailSend({ tenantId: 't1', campaignId: 'c1', recipientEmail: 'x@x.com', recipientHash: 'h1' }) - const { Queue: MockQueue } = await import('bullmq') - expect(MockQueue).toHaveBeenCalledWith('email:send', expect.anything()) + it('gibt err zurück wenn Job ohne ID erstellt wird', async () => { + mockAdd.mockResolvedValueOnce({ id: undefined }) + const result = await enqueueEmailSend({ + tenantId: 't1', campaignId: 'c1', recipientEmail: 'x@x.com', recipientHash: 'h1', + }, emailSendQueue) + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error.message).toContain('ohne ID') }) }) diff --git a/src/queues/email-send.queue.ts b/src/queues/email-send.queue.ts index b3325be..a0b5c62 100644 --- a/src/queues/email-send.queue.ts +++ b/src/queues/email-send.queue.ts @@ -13,29 +13,21 @@ const connection = { port: Number(process.env.REDIS_PORT ?? 6379), } -let _queue: Queue | null = null +export const emailSendQueue = new Queue('email-send', { connection }) -function getQueue(): Queue { - if (!_queue) { - _queue = new Queue('email:send', { connection }) - } - return _queue -} - -/** Nur für Tests — setzt den Queue-Singleton zurück */ -export function resetQueueForTesting(): void { - _queue = null -} - -export async function enqueueEmailSend(data: EmailSendJobData): Promise> { +export async function enqueueEmailSend( + data: EmailSendJobData, + queue: Queue = emailSendQueue +): Promise> { try { - const job = await getQueue().add('send', data, { + const job = await queue.add('send', data, { attempts: 3, backoff: { type: 'exponential', delay: 2000 }, removeOnComplete: 100, removeOnFail: { count: 500 }, }) - return ok(job.id ?? 'unknown') + if (!job.id) return err(new Error('Job wurde ohne ID erstellt')) + return ok(job.id) } catch (e) { return err(e instanceof Error ? e : new Error(String(e))) }