fix: safeInsertEvent verhindert Doppelversand, Union-Typ für eventType, Privacy-Tests ergänzt
This commit is contained in:
@@ -65,10 +65,15 @@ describe('processEmailSendJob', () => {
|
||||
expect(clickhouse.insert).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
values: expect.arrayContaining([
|
||||
expect.objectContaining({ event_type: 'suppressed' }),
|
||||
expect.objectContaining({
|
||||
event_type: 'suppressed',
|
||||
recipient_hash: 'abc123hash',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
const insertCall = vi.mocked(clickhouse.insert).mock.calls[0][0]
|
||||
expect(JSON.stringify(insertCall.values)).not.toContain('empfaenger@example.com')
|
||||
})
|
||||
|
||||
it('gibt err zurück wenn Kampagne nicht gefunden', async () => {
|
||||
@@ -117,4 +122,15 @@ describe('processEmailSendJob', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('ClickHouse-Fehler nach erfolgreichem SMTP führt nicht zu err (verhindert Doppelversand)', async () => {
|
||||
vi.mocked(getCampaign).mockResolvedValue({ ok: true, data: mockCampaign })
|
||||
vi.mocked(checkSuppression).mockResolvedValue(false)
|
||||
vi.mocked(sendEmail).mockResolvedValue({ ok: true, data: undefined })
|
||||
vi.mocked(clickhouse.insert).mockRejectedValueOnce(new Error('ClickHouse down'))
|
||||
|
||||
const result = await processEmailSendJob(jobData)
|
||||
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function processEmailSendJob(data: EmailSendJobData): Promise<Resul
|
||||
// Suppression-Check ist PFLICHT — kein Opt-out-Empfänger darf E-Mail erhalten
|
||||
const suppressed = await checkSuppression(data.tenantId, data.recipientEmail)
|
||||
if (suppressed) {
|
||||
await insertEvent('suppressed', data)
|
||||
await safeInsertEvent('suppressed', data)
|
||||
return ok(undefined)
|
||||
}
|
||||
|
||||
@@ -31,26 +31,31 @@ export async function processEmailSendJob(data: EmailSendJobData): Promise<Resul
|
||||
|
||||
if (!sendResult.ok) return err(sendResult.error)
|
||||
|
||||
await insertEvent('sent', data)
|
||||
// Analytics-Fehler nicht an BullMQ weitergeben — verhindert Doppelversand bei Retry
|
||||
await safeInsertEvent('sent', data)
|
||||
return ok(undefined)
|
||||
}
|
||||
|
||||
async function insertEvent(eventType: string, data: EmailSendJobData): Promise<void> {
|
||||
async function safeInsertEvent(
|
||||
eventType: 'sent' | 'suppressed',
|
||||
data: EmailSendJobData
|
||||
): Promise<void> {
|
||||
try {
|
||||
await clickhouse.insert({
|
||||
table: 'email_events',
|
||||
values: [
|
||||
{
|
||||
values: [{
|
||||
event_type: eventType,
|
||||
tenant_id: data.tenantId,
|
||||
campaign_id: data.campaignId,
|
||||
// Datenschutz: nur Hash wird gespeichert — keine Klartext-E-Mail-Adresse in ClickHouse
|
||||
recipient_hash: data.recipientHash,
|
||||
timestamp: new Date().toISOString(),
|
||||
metadata: {},
|
||||
},
|
||||
],
|
||||
}],
|
||||
format: 'JSONEachRow',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(JSON.stringify({ level: 'warn', msg: 'ClickHouse insert fehlgeschlagen', error: String(e) }))
|
||||
}
|
||||
}
|
||||
|
||||
const connection = {
|
||||
|
||||
Reference in New Issue
Block a user