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:
@@ -131,6 +131,48 @@ test('listSharedNumberGroups agrupa por equivalencia e reflete override', () =>
|
||||
db.close();
|
||||
});
|
||||
|
||||
// equivalentPhoneNumbers nao e simetrica: 5575933334444 expande para
|
||||
// 557533334444 (remove o nono digito sem condicao), mas 557533334444 nao volta
|
||||
// para 5575933334444 (o local '33334444' esta fora da faixa 6-9). O par
|
||||
// precisa ser detectado, agrupado e bloqueado nos dois sentidos.
|
||||
test('par 12/13 digitos assimetrico compartilha numero nos dois sentidos', () => {
|
||||
const db = openDb(':memory:');
|
||||
const insert = createLeadInserter(db);
|
||||
insert(sampleLead({ dedup_key: 'assim-a', empresa: 'Empresa A', whatsapp: '557533334444' }));
|
||||
insert(sampleLead({ dedup_key: 'assim-b', empresa: 'Empresa B', whatsapp: '5575933334444' }));
|
||||
insert(sampleLead({ dedup_key: 'assim-solo', empresa: 'Empresa Solo', whatsapp: '5511999990001' }));
|
||||
|
||||
for (const numero of ['557533334444', '5575933334444']) {
|
||||
assert.equal(database.isNumberSharedAcrossLeads(db, numero), true, numero);
|
||||
assert.deepEqual(
|
||||
database.leadsSharingNumber(db, numero).map((lead) => lead.empresa).sort(),
|
||||
['Empresa A', 'Empresa B'],
|
||||
numero
|
||||
);
|
||||
}
|
||||
assert.equal(database.isNumberSharedAcrossLeads(db, '5511999990001'), false);
|
||||
|
||||
const groups = database.listSharedNumberGroups(db);
|
||||
assert.equal(groups.length, 1);
|
||||
assert.deepEqual(groups[0].leads.map((lead) => lead.empresa).sort(), ['Empresa A', 'Empresa B']);
|
||||
|
||||
// Envio manual bloqueado pelas duas formas, e liberado pelas duas assim que o
|
||||
// operador revisa o grupo unico da tela de Configuracoes.
|
||||
for (const numero of ['557533334444', '5575933334444']) {
|
||||
assert.throws(
|
||||
() => reserveOutreachSend(db, { numero, mensagem: 'oi' }),
|
||||
(error) => error.statusCode === 409,
|
||||
numero
|
||||
);
|
||||
}
|
||||
database.addSharedNumberOverride(db, groups[0].numero, 'contabilidade confirmada');
|
||||
for (const numero of ['557533334444', '5575933334444']) {
|
||||
assert.equal(database.isSharedNumberOverridden(db, numero), true, numero);
|
||||
assert.ok(reserveOutreachSend(db, { numero, mensagem: 'oi' }) > 0, numero);
|
||||
}
|
||||
db.close();
|
||||
});
|
||||
|
||||
test('v15 preserva linhas legadas ao migrar uma base populada em v14', () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'campaign-v15-'));
|
||||
const dbPath = path.join(tempDir, 'legacy.sqlite');
|
||||
|
||||
Reference in New Issue
Block a user