Fix final-review findings: last-mile shared-number recheck, symmetric canonical key, per-row treatment query, JS/SQL agreement test
The last-mile validator in campaign-worker.mjs revalidated opt-out/inbound/ dedupe right before sending but never rechecked shared-number state, so re-blocking a released number after scheduling didn't stop the send. equivalentPhoneNumbers isn't symmetric for all 8/9-digit pairs, which made isNumberSharedAcrossLeads and listSharedNumberGroups disagree for a 12-digit local outside the 6-9 range vs its 13-digit counterpart: one side of the pair could be enforcement-blocked while invisible and unreleasable in the review screen. A new canonicalPhoneKey/canonicalPhoneVariants pair in phone.mjs (plus SQL mirrors) makes the matching relation symmetric by construction. listSharedNumberGroups did a full leads-table scan on every listLeads/getLead call, the app's primary paginated screen. Replaced with per-row correlated subqueries against idx_leads_whatsapp so cost tracks the page, not the table. normalizedSql/equivalentSql (SQL) duplicated equivalentPhoneNumbers (JS) with no test proving they agreed; moved into phone.mjs alongside their canonical counterparts and added tests/phone.test.mjs asserting agreement across a shared number set, including the asymmetric case.
This commit is contained in:
@@ -576,6 +576,37 @@ test('numero compartilhado por mais de um lead fica fora da fila ate liberacao m
|
||||
db.close();
|
||||
});
|
||||
|
||||
// 557533334444 vs 5575933334444: equivalentPhoneNumbers so relaciona o segundo
|
||||
// com o primeiro. Os dois precisam ficar fora da fila.
|
||||
test('par 12/13 digitos assimetrico fica fora da fila nos dois sentidos', async () => {
|
||||
const { db, campaign } = setupCampaign();
|
||||
const doze = insertLead(db, { key: 'assim-12', whatsapp: '557533334444', score: 100 });
|
||||
const treze = insertLead(db, { key: 'assim-13', whatsapp: '5575933334444', score: 99 });
|
||||
const solo = insertLead(db, { key: 'assim-solo', whatsapp: '5575992220000', score: 80 });
|
||||
for (const numero of ['557533334444', '5575933334444', '5575992220000']) confirmed(db, numero);
|
||||
|
||||
await prepareDailyQueue({
|
||||
db,
|
||||
whatsapp: noSendWhatsapp(async () => { throw new Error('todos os numeros possuem cache'); }),
|
||||
campaignId: campaign.id,
|
||||
now: new Date('2026-07-29T14:00:00-03:00')
|
||||
});
|
||||
assert.deepEqual(listCampaignQueue(db, campaign.id).map(({ lead_id }) => lead_id), [solo]);
|
||||
|
||||
addSharedNumberOverride(db, '557533334444', 'contabilidade confirmada');
|
||||
await prepareDailyQueue({
|
||||
db,
|
||||
whatsapp: noSendWhatsapp(async () => { throw new Error('todos os numeros possuem cache'); }),
|
||||
campaignId: campaign.id,
|
||||
now: new Date('2026-07-29T14:00:00-03:00')
|
||||
});
|
||||
assert.deepEqual(
|
||||
listCampaignQueue(db, campaign.id).map(({ lead_id }) => lead_id).sort((a, b) => a - b),
|
||||
[doze, treze, solo].sort((a, b) => a - b)
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('variantes 8 e 9 digitos do mesmo numero contam como compartilhado na fila', async () => {
|
||||
const { db, campaign } = setupCampaign();
|
||||
const nineDigits = insertLead(db, { key: 'nine-digits', whatsapp: '5575999091780', score: 100 });
|
||||
|
||||
Reference in New Issue
Block a user