Fix opt-out asymmetric 8/9-digit bypass letting sends reach blocked numbers
isOptedOut/addOptOut/removeOptOut used equivalentPhoneNumbers, which is not symmetric for a 12-digit local outside the 6-9 range vs its 13-digit counterpart (same root cause already fixed for shared-number detection). An opt-out registered against one form of such a number silently failed to block sends to a lead whose number was stored in the other form, on both the manual send path (reserveOutreachSend) and, independently, in two inlined checks that bypassed isOptedOut entirely: the last-mile recheck in campaign-worker.mjs's reserveCampaignQueueSend, and the opt-out NOT EXISTS clause in campaign-queue.mjs's candidate query. All three now use the symmetric canonicalPhoneVariants key already introduced for shared-number overrides, and the worker's last-mile check now calls isOptedOut directly instead of re-deriving its own number list. Found via a full-project audit; reproduced and verified before fixing.
This commit is contained in:
@@ -69,6 +69,35 @@ test('opt-out bloqueia e remove as formas equivalentes com e sem nono digito', a
|
||||
db.close();
|
||||
});
|
||||
|
||||
// equivalentPhoneNumbers nao e simetrica para um par 12/13 digitos fora da
|
||||
// faixa 6-9 (ver phone.mjs). addOptOut/isOptedOut usavam essa expansao
|
||||
// diretamente: bloquear numa forma do numero podia nao bloquear a outra forma
|
||||
// do mesmo numero, deixando passar um envio para quem pediu para nao receber.
|
||||
test('opt-out bloqueia os dois lados de um par 12/13 digitos assimetrico', async () => {
|
||||
const { openDb, addOptOut, isOptedOut, removeOptOut, reserveOutreachSend } = await import('../src/db.mjs');
|
||||
const db = openDb(':memory:');
|
||||
db.prepare(
|
||||
`INSERT INTO leads (dedup_key, empresa, whatsapp, telefone_valido)
|
||||
VALUES ('assim-optout', 'Empresa Assimetrica', '557533334444', 1)`
|
||||
).run();
|
||||
|
||||
addOptOut(db, '5575933334444', 'pedido do contato');
|
||||
for (const numero of ['557533334444', '5575933334444']) {
|
||||
assert.equal(isOptedOut(db, numero), true, numero);
|
||||
assert.throws(
|
||||
() => reserveOutreachSend(db, { numero, mensagem: 'oi' }),
|
||||
(error) => error.statusCode === 409,
|
||||
numero
|
||||
);
|
||||
}
|
||||
|
||||
assert.equal(removeOptOut(db, '557533334444'), true);
|
||||
for (const numero of ['557533334444', '5575933334444']) {
|
||||
assert.equal(isOptedOut(db, numero), false, numero);
|
||||
}
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('listagem e detalhe derivam o estado da verificacao equivalente mais recente', async () => {
|
||||
const { openDb, saveWhatsAppVerification } = await import('../src/db.mjs');
|
||||
const { getLead, listLeads } = await import('../src/treatment/queries.mjs');
|
||||
|
||||
Reference in New Issue
Block a user