@ecdt/server-common 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -83
- package/package.json +1 -1
- package/src/index.d.ts +82 -25
- package/src/index.js +2 -3
- package/src/services/motorAvaliadorService.js +443 -0
- package/test/motorAvaliadorService.test.js +222 -0
- package/src/services/cookieService.js +0 -43
- package/src/services/motorEventosService.js +0 -103
- package/test/cookieService.test.js +0 -59
- package/test/motorEventosService.test.js +0 -310
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const { test, describe } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
avaliarGatilhos,
|
|
6
|
+
pontuar,
|
|
7
|
+
casaPublico,
|
|
8
|
+
normalizarPorte,
|
|
9
|
+
horarioComercial,
|
|
10
|
+
} = require('../src/services/motorAvaliadorService.js');
|
|
11
|
+
|
|
12
|
+
// Fonte de leitura dublada. Sem os cinco metodos a avaliacao recusa a chamada, entao o dublê
|
|
13
|
+
// declara todos e o teste sobrescreve o que interessa.
|
|
14
|
+
const fonteDe = (respostas = {}) => ({
|
|
15
|
+
levantouMaoRecentemente: async () => respostas.levantouMao ?? null,
|
|
16
|
+
contagemAgrupada: async () => respostas.contagem ?? { total: 0 },
|
|
17
|
+
contagemDeTipos: async () => respostas.contagem ?? { total: 0 },
|
|
18
|
+
ultimoDisparo: async () => respostas.ultimoDisparo ?? undefined,
|
|
19
|
+
contagensNaJanela: async () => respostas.contagens ?? [],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const EVENTO = {
|
|
23
|
+
eventoId: 'e1',
|
|
24
|
+
tipo: 'desbloqueou_empresa',
|
|
25
|
+
idUsuario: 1,
|
|
26
|
+
// Terca-feira, 14h BRT: dentro do horario comercial e fora de feriado.
|
|
27
|
+
dtEvento: new Date('2026-09-22T17:00:00.000Z'),
|
|
28
|
+
payload: { setor: 'farmacias' },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const PERFIL = { temConta: true, cargo: null, porte: null };
|
|
32
|
+
|
|
33
|
+
const catalogoCom = (gatilhos, tipos = { desbloqueou_empresa: { peso: 10 } }) => ({
|
|
34
|
+
tipos,
|
|
35
|
+
acoes: { acao_x: { canal: 'n8n', ativo: true, params: {} } },
|
|
36
|
+
gatilhos,
|
|
37
|
+
janelaEmDias: 30,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const gatilho = (extra) => ({
|
|
41
|
+
nome: 'g',
|
|
42
|
+
disparo: 'imediato',
|
|
43
|
+
condicao: { tipos: ['desbloqueou_empresa'] },
|
|
44
|
+
acoes: [{ acao: 'acao_x' }],
|
|
45
|
+
cooldownH: null,
|
|
46
|
+
...extra,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('a fonte e contrato, nao sugestao', () => {
|
|
50
|
+
test('fonte sem um dos cinco metodos e recusada, dizendo qual falta', async () => {
|
|
51
|
+
const incompleta = { ...fonteDe() };
|
|
52
|
+
delete incompleta.contagensNaJanela;
|
|
53
|
+
await assert.rejects(
|
|
54
|
+
() => avaliarGatilhos(EVENTO, catalogoCom([gatilho()]), PERFIL, null, incompleta),
|
|
55
|
+
/fonte sem os métodos contagensNaJanela/
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('fonte ausente tambem e recusada, e nao estoura no meio da avaliacao', async () => {
|
|
60
|
+
await assert.rejects(
|
|
61
|
+
() => avaliarGatilhos(EVENTO, catalogoCom([gatilho()]), PERFIL, null, undefined),
|
|
62
|
+
/fonte sem os métodos/
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('catalogo e casos de borda', () => {
|
|
68
|
+
test('catalogo vazio nao dispara e nao lanca', async () => {
|
|
69
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([]), PERFIL, null, fonteDe());
|
|
70
|
+
assert.deepEqual(r, { disparos: [], recusas: [] });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('tipo fora do catalogo nao fecha o imediato', async () => {
|
|
74
|
+
const catalogo = catalogoCom([gatilho()], {});
|
|
75
|
+
const r = await avaliarGatilhos(EVENTO, catalogo, PERFIL, null, fonteDe());
|
|
76
|
+
assert.equal(r.disparos.length, 0);
|
|
77
|
+
assert.match(r.recusas[0].motivo, /condição não fechou/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('contagem com janela invalida recusa com motivo, sem consultar', async () => {
|
|
81
|
+
const g = gatilho({ disparo: 'contagem', condicao: { tipos: ['desbloqueou_empresa'], quantidade: 2, janela: 'semana' } });
|
|
82
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonteDe());
|
|
83
|
+
assert.equal(r.recusas[0].motivo, 'janela inválida no cadastro');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('agrupamento sem o campo no payload recusa dizendo qual campo', async () => {
|
|
87
|
+
const g = gatilho({
|
|
88
|
+
disparo: 'contagem',
|
|
89
|
+
condicao: { tipos: ['desbloqueou_empresa'], quantidade: 2, janela: '7d', agrupar_por: 'payload.regiao' },
|
|
90
|
+
});
|
|
91
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonteDe());
|
|
92
|
+
assert.equal(r.recusas[0].motivo, 'evento sem o campo regiao do agrupamento');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('pontuacao sem pesos no gatilho cai no peso do catalogo', async () => {
|
|
96
|
+
const g = gatilho({ disparo: 'pontuacao', condicao: { limiar: 30 } });
|
|
97
|
+
const fonte = fonteDe({ contagens: [{ tipo: 'desbloqueou_empresa', total: '3' }] });
|
|
98
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonte);
|
|
99
|
+
assert.equal(r.disparos.length, 1);
|
|
100
|
+
assert.equal(r.disparos[0].contexto.pontuacao, 30);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('as tres formas de disparo', () => {
|
|
105
|
+
test('imediato fecha com o tipo no catalogo', async () => {
|
|
106
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([gatilho()]), PERFIL, null, fonteDe());
|
|
107
|
+
assert.equal(r.disparos.length, 1);
|
|
108
|
+
assert.equal(r.disparos[0].contexto.evento, 'desbloqueou_empresa');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('contagem fecha no limite e nao abaixo', async () => {
|
|
112
|
+
const g = gatilho({ disparo: 'contagem', condicao: { tipos: ['desbloqueou_empresa'], quantidade: 3, janela: '24h' } });
|
|
113
|
+
const abaixo = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonteDe({ contagem: { total: '2' } }));
|
|
114
|
+
assert.equal(abaixo.disparos.length, 0);
|
|
115
|
+
const noLimite = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonteDe({ contagem: { total: '3' } }));
|
|
116
|
+
assert.equal(noLimite.disparos.length, 1);
|
|
117
|
+
assert.equal(noLimite.disparos[0].contexto.contagem, 3);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('o disparo NAO carrega identificador: a identidade e do chamador', async () => {
|
|
121
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([gatilho()]), PERFIL, null, fonteDe());
|
|
122
|
+
assert.deepEqual(Object.keys(r.disparos[0]).sort(), ['contexto', 'gatilho']);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('gates', () => {
|
|
127
|
+
test('horario comercial barra fora da janela e a pontuacao segue contando', async () => {
|
|
128
|
+
const g = gatilho({ condicao: { tipos: ['desbloqueou_empresa'], horario_comercial: true } });
|
|
129
|
+
const domingo = { ...EVENTO, dtEvento: new Date('2026-09-20T17:00:00.000Z') };
|
|
130
|
+
const r = await avaliarGatilhos(domingo, catalogoCom([g]), PERFIL, null, fonteDe());
|
|
131
|
+
assert.match(r.recusas[0].motivo, /horário comercial: fim de semana/);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('intervalo entre disparos barra dentro da janela', async () => {
|
|
135
|
+
const g = gatilho({ cooldownH: 24 });
|
|
136
|
+
const fonte = fonteDe({ ultimoDisparo: { dt_gatilho: new Date('2026-09-22T10:00:00.000Z') } });
|
|
137
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonte);
|
|
138
|
+
assert.match(r.recusas[0].motivo, /em intervalo de 24h/);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('sem ultimo disparo, o intervalo nao barra — e o caso da simulacao', async () => {
|
|
142
|
+
const g = gatilho({ cooldownH: 24 });
|
|
143
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([g]), PERFIL, null, fonteDe());
|
|
144
|
+
assert.equal(r.disparos.length, 1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('gate pos-levantada so consulta quando ha acao in_page e e-mail', async () => {
|
|
148
|
+
let consultas = 0;
|
|
149
|
+
const fonte = { ...fonteDe(), levantouMaoRecentemente: async () => { consultas += 1; return true; } };
|
|
150
|
+
const catalogo = catalogoCom([gatilho()]);
|
|
151
|
+
catalogo.acoes.acao_x.canal = 'in_page';
|
|
152
|
+
const comEmail = await avaliarGatilhos(EVENTO, catalogo, PERFIL, 'a@b.c', fonte);
|
|
153
|
+
assert.equal(consultas, 1);
|
|
154
|
+
assert.match(comEmail.recusas[0].motivo, /levantou a mão/);
|
|
155
|
+
await avaliarGatilhos(EVENTO, catalogo, PERFIL, null, fonte);
|
|
156
|
+
assert.equal(consultas, 1, 'sem e-mail nao consulta');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('A24 — o mais especifico vence', () => {
|
|
161
|
+
test('o preterido aparece como preterido, dizendo por quem perdeu', async () => {
|
|
162
|
+
const geral = gatilho({ nome: 'geral', disparo: 'pontuacao', condicao: { limiar: 10 } });
|
|
163
|
+
const especifico = gatilho({
|
|
164
|
+
nome: 'especifico', disparo: 'pontuacao',
|
|
165
|
+
condicao: { limiar: 10, publico: { com_conta: true } },
|
|
166
|
+
});
|
|
167
|
+
const fonte = fonteDe({ contagens: [{ tipo: 'desbloqueou_empresa', total: '3' }] });
|
|
168
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom([geral, especifico]), PERFIL, null, fonte);
|
|
169
|
+
assert.equal(r.disparos.length, 1);
|
|
170
|
+
assert.equal(r.disparos[0].gatilho.nome, 'especifico');
|
|
171
|
+
assert.deepEqual(r.disparos[0].contexto.gatilhos_preteridos, ['geral']);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('empate de especificidade desempata por nome, e e deterministico', async () => {
|
|
175
|
+
const a = gatilho({ nome: 'aaa', disparo: 'pontuacao', condicao: { limiar: 10 } });
|
|
176
|
+
const b = gatilho({ nome: 'bbb', disparo: 'pontuacao', condicao: { limiar: 10 } });
|
|
177
|
+
const fonte = fonteDe({ contagens: [{ tipo: 'desbloqueou_empresa', total: '3' }] });
|
|
178
|
+
for (const ordem of [[a, b], [b, a]]) {
|
|
179
|
+
const r = await avaliarGatilhos(EVENTO, catalogoCom(ordem), PERFIL, null, fonte);
|
|
180
|
+
assert.equal(r.disparos[0].gatilho.nome, 'aaa');
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('as funcoes puras que o avaliador usa', () => {
|
|
186
|
+
test('bucket agrupa antes de pontuar, e o resto e descartado', () => {
|
|
187
|
+
const tipos = { lista: { peso: 25, bucket: 3 } };
|
|
188
|
+
assert.equal(pontuar({ lista: 2 }, tipos).total, 0);
|
|
189
|
+
assert.equal(pontuar({ lista: 3 }, tipos).total, 25);
|
|
190
|
+
assert.equal(pontuar({ lista: 5 }, tipos).total, 25);
|
|
191
|
+
assert.equal(pontuar({ lista: 6 }, tipos).total, 50);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('peso do gatilho sobrescreve o do catalogo (A25)', () => {
|
|
195
|
+
assert.equal(pontuar({ a: 1 }, { a: { peso: 5 } }, { a: 50 }).total, 50);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('porte legado com zero a esquerda casa a faixa canonica', () => {
|
|
199
|
+
assert.equal(normalizarPorte('01-04'), '1-4');
|
|
200
|
+
assert.equal(normalizarPorte('acima de 500'), 'Mais que 500');
|
|
201
|
+
assert.equal(normalizarPorte('sei la'), '', 'irreconhecivel volta vazio, nao a primeira faixa');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('publico sem criterio e fallback com especificidade zero', () => {
|
|
205
|
+
assert.deepEqual(casaPublico(undefined, PERFIL), {
|
|
206
|
+
casa: true, especificidade: 0, motivo: 'sem criterio (fallback)'.replace('criterio', 'critério'),
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('plano premium nao casa ninguem na v1, e o motivo diz por que', () => {
|
|
211
|
+
const r = casaPublico({ plano: ['premium'] }, PERFIL);
|
|
212
|
+
assert.equal(r.casa, false);
|
|
213
|
+
assert.match(r.motivo, /usuário do site é gratuito/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('feriado nacional fecha a janela mesmo em dia util', () => {
|
|
217
|
+
// 7 de setembro de 2026 e uma segunda-feira.
|
|
218
|
+
assert.deepEqual(horarioComercial(new Date('2026-09-07T17:00:00.000Z')), {
|
|
219
|
+
dentro: false, motivo: 'feriado nacional',
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const TAMANHO_MAXIMO_PADRAO = 100;
|
|
2
|
-
|
|
3
|
-
function sanitizarId(valor, maxLen = TAMANHO_MAXIMO_PADRAO) {
|
|
4
|
-
if (typeof valor !== "string") return null;
|
|
5
|
-
|
|
6
|
-
const limpo = valor.trim();
|
|
7
|
-
return limpo ? limpo.slice(0, maxLen) : null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function lerCookie(cabecalho, nome) {
|
|
11
|
-
if (typeof cabecalho !== "string" || typeof nome !== "string") return null;
|
|
12
|
-
|
|
13
|
-
for (const parte of cabecalho.split(";")) {
|
|
14
|
-
const separador = parte.indexOf("=");
|
|
15
|
-
if (separador === -1) continue;
|
|
16
|
-
if (parte.slice(0, separador).trim() !== nome) continue;
|
|
17
|
-
return parte.slice(separador + 1).trim();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function idDoCookie(cabecalho, nome, { maxLen = TAMANHO_MAXIMO_PADRAO } = {}) {
|
|
24
|
-
const bruto = lerCookie(cabecalho, nome);
|
|
25
|
-
if (!bruto) return null;
|
|
26
|
-
|
|
27
|
-
let valor = bruto;
|
|
28
|
-
try {
|
|
29
|
-
valor = decodeURIComponent(bruto);
|
|
30
|
-
} catch (_erroDeEncoding) {
|
|
31
|
-
valor = bruto;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const conteudo = JSON.parse(valor);
|
|
36
|
-
const id = conteudo && typeof conteudo === "object" ? conteudo.id : conteudo;
|
|
37
|
-
return sanitizarId(typeof id === "string" ? id : String(id ?? ""), maxLen);
|
|
38
|
-
} catch (_naoEhJson) {
|
|
39
|
-
return sanitizarId(valor, maxLen);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = { lerCookie, idDoCookie, sanitizarId };
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
const { idDoCookie, sanitizarId } = require("./cookieService.js");
|
|
2
|
-
|
|
3
|
-
const TIMEOUT_PADRAO_MS = 2000;
|
|
4
|
-
|
|
5
|
-
function urlConfigurada(url) {
|
|
6
|
-
const configurada = url ?? process.env.MOTOR_EVENTOS_URL;
|
|
7
|
-
|
|
8
|
-
if (typeof configurada !== "string" || configurada.trim() === "") {
|
|
9
|
-
console.warn("motorEventos: url do coletor ausente, evento nao publicado");
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return configurada.trim();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function textoNaoVazio(valor) {
|
|
17
|
-
return typeof valor === "string" && valor.trim() !== "" ? valor.trim() : null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function publicarEvento({
|
|
21
|
-
tipo,
|
|
22
|
-
autorizacao,
|
|
23
|
-
idVisitante,
|
|
24
|
-
payload,
|
|
25
|
-
url,
|
|
26
|
-
timeoutMs = TIMEOUT_PADRAO_MS,
|
|
27
|
-
aoFalhar = (mensagem) => console.error(mensagem),
|
|
28
|
-
} = {}) {
|
|
29
|
-
try {
|
|
30
|
-
const destino = urlConfigurada(url);
|
|
31
|
-
const visitante = textoNaoVazio(idVisitante);
|
|
32
|
-
|
|
33
|
-
if (!destino || !tipo || (!autorizacao && !visitante)) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const cabecalhos = { "Content-Type": "application/json" };
|
|
38
|
-
if (autorizacao) {
|
|
39
|
-
cabecalhos.Authorization = autorizacao;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const corpo = {
|
|
43
|
-
tipo,
|
|
44
|
-
dt_evento: new Date().toISOString(),
|
|
45
|
-
payload: payload ?? {},
|
|
46
|
-
};
|
|
47
|
-
if (visitante) {
|
|
48
|
-
corpo.id_visitante = visitante;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fetch(destino, {
|
|
52
|
-
method: "POST",
|
|
53
|
-
headers: cabecalhos,
|
|
54
|
-
body: JSON.stringify(corpo),
|
|
55
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
56
|
-
})
|
|
57
|
-
.then((resposta) => {
|
|
58
|
-
if (!resposta.ok) {
|
|
59
|
-
aoFalhar(`motorEventos: ${tipo} recusado com status ${resposta.status}`);
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
.catch((erro) => {
|
|
63
|
-
aoFalhar(`motorEventos: ${tipo} nao publicado (${erro?.message})`);
|
|
64
|
-
});
|
|
65
|
-
} catch (erro) {
|
|
66
|
-
try {
|
|
67
|
-
aoFalhar(`motorEventos: ${tipo} nao publicado (${erro?.message})`);
|
|
68
|
-
} catch (_erroNoCallback) {
|
|
69
|
-
/* empty */
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Publica o evento a partir da requisicao que o gerou: tira o token do header
|
|
76
|
-
* Authorization e o identificador anonimo do corpo ou do cookie, nessa ordem.
|
|
77
|
-
* Nunca lanca, nem quando `req` nao tem headers nem corpo — o evento e
|
|
78
|
-
* secundario ao fluxo, e boa parte das chamadas acontece depois de uma escrita
|
|
79
|
-
* ja efetivada.
|
|
80
|
-
*/
|
|
81
|
-
function publicarEventoDaRequisicao(req, {
|
|
82
|
-
tipo,
|
|
83
|
-
payload,
|
|
84
|
-
cookieVisitante = process.env.MOTOR_EVENTOS_COOKIE_VISITANTE,
|
|
85
|
-
...resto
|
|
86
|
-
} = {}) {
|
|
87
|
-
try {
|
|
88
|
-
const doCorpo = sanitizarId(req?.body?.id_visitante);
|
|
89
|
-
const doCookie = cookieVisitante ? idDoCookie(req?.headers?.cookie, cookieVisitante) : null;
|
|
90
|
-
|
|
91
|
-
publicarEvento({
|
|
92
|
-
tipo,
|
|
93
|
-
payload,
|
|
94
|
-
autorizacao: req?.headers?.authorization,
|
|
95
|
-
idVisitante: doCorpo ?? doCookie,
|
|
96
|
-
...resto,
|
|
97
|
-
});
|
|
98
|
-
} catch (erro) {
|
|
99
|
-
console.error(`motorEventos: ${tipo} nao publicado (${erro?.message})`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = { publicarEvento, publicarEventoDaRequisicao };
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
const { test, describe } = require('node:test');
|
|
2
|
-
const assert = require('node:assert/strict');
|
|
3
|
-
|
|
4
|
-
const { lerCookie, idDoCookie, sanitizarId } = require('../src/services/cookieService.js');
|
|
5
|
-
|
|
6
|
-
const comCookie = (nome, valor) => `_ga=GA1.1.9; ${nome}=${encodeURIComponent(valor)}; outro=x`;
|
|
7
|
-
|
|
8
|
-
describe('sanitizarId', () => {
|
|
9
|
-
test('devolve null para vazio, espaco em branco ou valor que nao e string', () => {
|
|
10
|
-
assert.equal(sanitizarId(''), null);
|
|
11
|
-
assert.equal(sanitizarId(' '), null);
|
|
12
|
-
assert.equal(sanitizarId(undefined), null);
|
|
13
|
-
assert.equal(sanitizarId(42), null);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('corta no tamanho maximo', () => {
|
|
17
|
-
assert.equal(sanitizarId('a'.repeat(300)).length, 100);
|
|
18
|
-
assert.equal(sanitizarId('a'.repeat(300), 10).length, 10);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('lerCookie', () => {
|
|
23
|
-
test('acha o cookie no meio do header', () => {
|
|
24
|
-
assert.equal(lerCookie('a=1; alvo=valor; b=2', 'alvo'), 'valor');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('nao casa com cookie de nome parecido', () => {
|
|
28
|
-
assert.equal(lerCookie('alvo_antigo=valor', 'alvo'), null);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('devolve null sem header, sem o cookie ou com header que nao e string', () => {
|
|
32
|
-
assert.equal(lerCookie(undefined, 'alvo'), null);
|
|
33
|
-
assert.equal(lerCookie('a=1', 'alvo'), null);
|
|
34
|
-
assert.equal(lerCookie({}, 'alvo'), null);
|
|
35
|
-
assert.equal(lerCookie('a=1', undefined), null);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('idDoCookie', () => {
|
|
40
|
-
test('extrai o id quando o cookie guarda JSON', () => {
|
|
41
|
-
assert.equal(idDoCookie(comCookie('trk', '{"id":"visitante-1"}'), 'trk'), 'visitante-1');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('aceita o cookie como string simples', () => {
|
|
45
|
-
assert.equal(idDoCookie(comCookie('trk', 'visitante-2'), 'trk'), 'visitante-2');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('devolve null quando o JSON nao tem id', () => {
|
|
49
|
-
assert.equal(idDoCookie(comCookie('trk', '{"outro":"x"}'), 'trk'), null);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('devolve null quando o cookie nao existe', () => {
|
|
53
|
-
assert.equal(idDoCookie('a=1', 'trk'), null);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('respeita o maxLen', () => {
|
|
57
|
-
assert.equal(idDoCookie(comCookie('trk', 'a'.repeat(300)), 'trk', { maxLen: 5 }).length, 5);
|
|
58
|
-
});
|
|
59
|
-
});
|