@ecdt/server-common 1.8.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 CHANGED
@@ -158,89 +158,6 @@ export default defineEventHandler(async (event) => {
158
158
 
159
159
  ---
160
160
 
161
- ### `publicarEvento(options)`
162
-
163
- Envia um evento para o coletor de eventos configurado, sem bloquear o fluxo que o gerou.
164
-
165
- ```js
166
- const { publicarEvento } = require('@ecdt/server-common');
167
-
168
- publicarEvento({
169
- tipo: 'meu_evento',
170
- autorizacao: req.headers.authorization,
171
- payload: { campo: 'valor' },
172
- });
173
- ```
174
-
175
- | Opção | Default | Descrição |
176
- |---|---|---|
177
- | `tipo` | — | Nome do evento, em `snake_case` |
178
- | `autorizacao` | — | Header `Authorization` da requisição que originou o evento |
179
- | `idVisitante` | — | Identificador anônimo, para quando não há token |
180
- | `payload` | `{}` | Campos do evento |
181
- | `url` | `process.env.MOTOR_EVENTOS_URL` | URL completa do coletor; a função não monta caminho nenhum |
182
- | `timeoutMs` | `2000` | Aborta a requisição |
183
- | `aoFalhar` | `console.error` | Recebe a mensagem quando a publicação não acontece |
184
-
185
- A identidade do usuário sai do token, nunca do corpo: o coletor a resolve a partir do `Authorization`
186
- repassado. Quando não há token — rota pública, visitante anônimo — vale o `idVisitante`, que vai no
187
- corpo como `id_visitante`. Ele não é identidade autenticada, é um identificador de correlação: forjá-lo
188
- não atribui nada à conta de ninguém, e por isso pode vir do corpo da requisição. Basta um dos dois.
189
-
190
- Sem `tipo`, ou sem nenhum dos dois identificadores, a função não faz nada; sem `url` ela ainda registra
191
- um `console.warn`, porque url ausente é configuração faltando e não um evento que não se aplica.
192
-
193
- **A função nunca lança e nunca devolve promise.** O evento é secundário ao fluxo que o gerou —
194
- falha de publicação não pode virar erro de quem chamou, e o retorno `undefined` impede que alguém
195
- consiga dar `await` e acoplar a latência do request ao motor.
196
-
197
- ---
198
-
199
- ### `publicarEventoDaRequisicao(req, options)`
200
-
201
- O mesmo publish, montado a partir da requisição que gerou o evento. Resolve o token pelo header
202
- `Authorization` e o identificador anônimo pelo corpo (`id_visitante`) ou pelo cookie, nessa ordem.
203
-
204
- ```js
205
- const { publicarEventoDaRequisicao } = require('@ecdt/server-common');
206
-
207
- publicarEventoDaRequisicao(req, {
208
- tipo: 'meu_evento',
209
- payload: { campo: 'valor' },
210
- });
211
- ```
212
-
213
- | Opção | Default | Descrição |
214
- |---|---|---|
215
- | `tipo`, `payload`, `url`, `timeoutMs`, `aoFalhar` | — | Idênticos ao `publicarEvento` |
216
- | `cookieVisitante` | `process.env.MOTOR_EVENTOS_COOKIE_VISITANTE` | Nome do cookie que guarda o identificador anônimo |
217
-
218
- Sem `cookieVisitante` configurado, só o corpo alimenta o `id_visitante`. Também não lança em
219
- nenhuma hipótese, inclusive com `req` sem `headers` nem `body`.
220
-
221
- ---
222
-
223
- ### `lerCookie`, `idDoCookie` e `sanitizarId`
224
-
225
- Leitura do header `Cookie` no servidor, sem dependência de framework.
226
-
227
- ```js
228
- const { idDoCookie } = require('@ecdt/server-common');
229
-
230
- const idVisitante = idDoCookie(req.headers.cookie, 'nome_do_cookie');
231
- ```
232
-
233
- | Função | Devolve |
234
- |---|---|
235
- | `lerCookie(cabecalho, nome)` | Valor cru do cookie, ou `null` |
236
- | `idDoCookie(cabecalho, nome, { maxLen })` | O identificador: aceita o cookie como string simples ou como JSON com campo `id`, decodifica o valor e corta em `maxLen` (100 por padrão) |
237
- | `sanitizarId(valor, maxLen)` | O mesmo corte e validação, para identificador que veio de outro lugar |
238
-
239
- O match do nome é exato, então `nome_antigo` não casa com `nome`. Nenhuma das três lança: entrada
240
- que não é string vira `null`.
241
-
242
- ---
243
-
244
161
  ## Instalação
245
162
 
246
163
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecdt/server-common",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "Conjunto de ferramentas e configurações comuns nos servidores da Econodata",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -136,28 +136,6 @@ export declare function renderMetrics(
136
136
  registry?: MetricsRegistry
137
137
  ): Promise<{ contentType: string; body: string }>;
138
138
 
139
- export interface PublicarEventoOptions {
140
- tipo: string;
141
- autorizacao?: string;
142
- idVisitante?: string;
143
- payload?: Record<string, unknown>;
144
- url?: string;
145
- timeoutMs?: number;
146
- aoFalhar?: (mensagem: string) => void;
147
- }
148
-
149
- export declare function publicarEvento(options?: PublicarEventoOptions): void;
150
-
151
- export interface PublicarEventoDaRequisicaoOptions
152
- extends Omit<PublicarEventoOptions, 'autorizacao' | 'idVisitante'> {
153
- cookieVisitante?: string;
154
- }
155
-
156
- export declare function publicarEventoDaRequisicao(
157
- req: unknown,
158
- options?: PublicarEventoDaRequisicaoOptions
159
- ): void;
160
-
161
139
  /**
162
140
  * Avaliação de gatilhos do motor — pura. As cinco leituras chegam em `fonte`, então a mesma função
163
141
  * serve a Function (consultas reais) e o simulador do backoffice (eventos de mentira).
@@ -250,17 +228,3 @@ export declare function pontuar(
250
228
  tipos: Record<string, ParamsDoTipo>,
251
229
  pesosDoGatilho?: Record<string, number>
252
230
  ): { total: number; porTipo: Record<string, number> };
253
-
254
- export interface IdDoCookieOptions {
255
- maxLen?: number;
256
- }
257
-
258
- export declare function lerCookie(cabecalho: unknown, nome: string): string | null;
259
-
260
- export declare function idDoCookie(
261
- cabecalho: unknown,
262
- nome: string,
263
- options?: IdDoCookieOptions
264
- ): string | null;
265
-
266
- export declare function sanitizarId(valor: unknown, maxLen?: number): string | null;
package/src/index.js CHANGED
@@ -3,9 +3,7 @@ const { devMktTokenSanitaze } = require("./services/tokenService.js");
3
3
  const { expressCors } = require("./services/corsService.js");
4
4
  const { setupGracefulShutdown } = require("./services/shutdownService.js");
5
5
  const { setupRedMetrics, initRedMetrics, redMetricsMiddleware, metricsHandler, renderMetrics } = require("./services/redMetricsService.js");
6
- const { publicarEvento, publicarEventoDaRequisicao } = require("./services/motorEventosService.js");
7
6
  const { avaliarGatilhos, pontuar, normalizarPorte } = require("./services/motorAvaliadorService.js");
8
- const { lerCookie, idDoCookie, sanitizarId } = require("./services/cookieService.js");
9
7
 
10
8
  function expressCommonMiddlewares({ cookieName } = {}){
11
9
  return [bodyParser.text(), bodyParser.json(), bodyParser.urlencoded({extended: false}), devMktTokenSanitaze({ cookieName })];
@@ -14,4 +12,4 @@ function expressCommonMiddlewares({ cookieName } = {}){
14
12
 
15
13
 
16
14
 
17
- module.exports = { expressCommonMiddlewares, expressCors, setupGracefulShutdown, setupRedMetrics, initRedMetrics, redMetricsMiddleware, metricsHandler, renderMetrics, publicarEvento, publicarEventoDaRequisicao, avaliarGatilhos, pontuar, normalizarPorte, lerCookie, idDoCookie, sanitizarId }
15
+ module.exports = { expressCommonMiddlewares, expressCors, setupGracefulShutdown, setupRedMetrics, initRedMetrics, redMetricsMiddleware, metricsHandler, renderMetrics, avaliarGatilhos, pontuar, normalizarPorte }
@@ -5,10 +5,6 @@
5
5
  * chegam no parâmetro `fonte`. É isso que permite a mesma função decidir em dois lugares — a
6
6
  * `events-engine`, que passa as consultas reais, e o simulador do backoffice, que passa uma
7
7
  * implementação em memória sobre eventos de mentira.
8
- *
9
- * Mora aqui, e não num pacote novo, porque o lado PRODUTOR do mesmo contrato já mora: o
10
- * `motorEventosService.js`, ao lado, é quem publica o evento. Produtor e avaliador do mesmo motor
11
- * no mesmo pacote é coerência; um repositório novo para duas dezenas de funções puras, não.
12
8
  */
13
9
 
14
10
  // ── porte ─────────────────────────────────────────────────────────────────────────────────────
@@ -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
- });
@@ -1,310 +0,0 @@
1
- const { test, describe, beforeEach, afterEach } = require('node:test');
2
- const assert = require('node:assert/strict');
3
-
4
- const {
5
- publicarEvento,
6
- publicarEventoDaRequisicao,
7
- } = require('../src/services/motorEventosService.js');
8
-
9
- const URL = 'http://coletor.interno/eventos';
10
-
11
- const esperarMicrotasks = () => new Promise((resolve) => setImmediate(resolve));
12
-
13
- describe('publicarEvento', () => {
14
- let fetchOriginal;
15
- let warnOriginal;
16
- let urlOriginal;
17
- let chamadas;
18
- let falhas;
19
- let avisos;
20
-
21
- const aoFalhar = (mensagem) => falhas.push(mensagem);
22
-
23
- beforeEach(() => {
24
- fetchOriginal = global.fetch;
25
- warnOriginal = console.warn;
26
- urlOriginal = process.env.MOTOR_EVENTOS_URL;
27
- chamadas = [];
28
- falhas = [];
29
- avisos = [];
30
- console.warn = (mensagem) => avisos.push(mensagem);
31
- global.fetch = (url, opcoes) => {
32
- chamadas.push({ url, opcoes });
33
- return Promise.resolve({ ok: true, status: 201 });
34
- };
35
- });
36
-
37
- afterEach(() => {
38
- global.fetch = fetchOriginal;
39
- console.warn = warnOriginal;
40
- if (urlOriginal === undefined) {
41
- delete process.env.MOTOR_EVENTOS_URL;
42
- } else {
43
- process.env.MOTOR_EVENTOS_URL = urlOriginal;
44
- }
45
- });
46
-
47
- test('envia POST na url configurada com o token de quem chamou', async () => {
48
- publicarEvento({
49
- tipo: 'meu_evento',
50
- autorizacao: 'Bearer jwt',
51
- payload: { campo: 'valor' },
52
- url: URL,
53
- aoFalhar,
54
- });
55
-
56
- await esperarMicrotasks();
57
-
58
- assert.equal(chamadas.length, 1);
59
- assert.equal(chamadas[0].url, URL);
60
- assert.equal(chamadas[0].opcoes.method, 'POST');
61
- assert.equal(chamadas[0].opcoes.headers.Authorization, 'Bearer jwt');
62
-
63
- const corpo = JSON.parse(chamadas[0].opcoes.body);
64
- assert.equal(corpo.tipo, 'meu_evento');
65
- assert.deepEqual(corpo.payload, { campo: 'valor' });
66
- assert.ok(!Number.isNaN(Date.parse(corpo.dt_evento)));
67
- assert.deepEqual(falhas, []);
68
- assert.deepEqual(avisos, []);
69
- });
70
-
71
- test('nao acrescenta caminho nenhum a url configurada', async () => {
72
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: 'http://coletor.interno/v2/x', aoFalhar });
73
- await esperarMicrotasks();
74
-
75
- assert.equal(chamadas[0].url, 'http://coletor.interno/v2/x');
76
- });
77
-
78
- test('cai na MOTOR_EVENTOS_URL quando a url nao e passada', async () => {
79
- process.env.MOTOR_EVENTOS_URL = URL;
80
-
81
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', aoFalhar });
82
- await esperarMicrotasks();
83
-
84
- assert.equal(chamadas[0].url, URL);
85
- });
86
-
87
- test('avisa e nao publica quando a url esta ausente', async () => {
88
- delete process.env.MOTOR_EVENTOS_URL;
89
-
90
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', aoFalhar });
91
- await esperarMicrotasks();
92
-
93
- assert.equal(chamadas.length, 0);
94
- assert.equal(avisos.length, 1);
95
- assert.match(avisos[0], /url do coletor ausente/);
96
- });
97
-
98
- test('avisa quando a url nao e string ou e so espaco', async () => {
99
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: 123, aoFalhar });
100
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: ' ', aoFalhar });
101
- await esperarMicrotasks();
102
-
103
- assert.equal(chamadas.length, 0);
104
- assert.equal(avisos.length, 2);
105
- });
106
-
107
- test('nao publica sem tipo, nem sem identidade nenhuma', async () => {
108
- publicarEvento({ autorizacao: 'Bearer jwt', url: URL, aoFalhar });
109
- publicarEvento({ tipo: 'meu_evento', url: URL, aoFalhar });
110
- publicarEvento({ tipo: 'meu_evento', url: URL, idVisitante: ' ', aoFalhar });
111
- await esperarMicrotasks();
112
-
113
- assert.equal(chamadas.length, 0);
114
- assert.deepEqual(falhas, []);
115
- assert.deepEqual(avisos, []);
116
- });
117
-
118
- test('publica so com idVisitante, sem header de autorizacao', async () => {
119
- publicarEvento({ tipo: 'meu_evento', idVisitante: 'visitante-1', url: URL, aoFalhar });
120
- await esperarMicrotasks();
121
-
122
- assert.equal(chamadas.length, 1);
123
- assert.equal(chamadas[0].opcoes.headers.Authorization, undefined);
124
- assert.equal(JSON.parse(chamadas[0].opcoes.body).id_visitante, 'visitante-1');
125
- });
126
-
127
- test('manda os dois quando tem token e visitante', async () => {
128
- publicarEvento({
129
- tipo: 'meu_evento',
130
- autorizacao: 'Bearer jwt',
131
- idVisitante: 'visitante-1',
132
- url: URL,
133
- aoFalhar,
134
- });
135
- await esperarMicrotasks();
136
-
137
- assert.equal(chamadas[0].opcoes.headers.Authorization, 'Bearer jwt');
138
- assert.equal(JSON.parse(chamadas[0].opcoes.body).id_visitante, 'visitante-1');
139
- });
140
-
141
- test('omite id_visitante do corpo quando nao ha visitante', async () => {
142
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: URL, aoFalhar });
143
- await esperarMicrotasks();
144
-
145
- assert.ok(!('id_visitante' in JSON.parse(chamadas[0].opcoes.body)));
146
- });
147
-
148
- test('nao quebra quando chamado sem argumento nenhum', () => {
149
- delete process.env.MOTOR_EVENTOS_URL;
150
-
151
- assert.doesNotThrow(() => publicarEvento());
152
- assert.equal(chamadas.length, 0);
153
- });
154
-
155
- test('nao propaga rejeicao do fetch e reporta a falha', async () => {
156
- global.fetch = () => Promise.reject(new Error('connect ECONNREFUSED'));
157
-
158
- assert.doesNotThrow(() =>
159
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: URL, aoFalhar })
160
- );
161
-
162
- await esperarMicrotasks();
163
-
164
- assert.equal(falhas.length, 1);
165
- assert.match(falhas[0], /meu_evento/);
166
- });
167
-
168
- test('reporta resposta de erro sem lancar', async () => {
169
- global.fetch = () => Promise.resolve({ ok: false, status: 400 });
170
-
171
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: URL, aoFalhar });
172
- await esperarMicrotasks();
173
-
174
- assert.equal(falhas.length, 1);
175
- assert.match(falhas[0], /400/);
176
- });
177
-
178
- test('nao propaga erro sincrono do fetch', () => {
179
- global.fetch = () => {
180
- throw new TypeError('opcoes invalidas');
181
- };
182
-
183
- assert.doesNotThrow(() =>
184
- publicarEvento({ tipo: 'meu_evento', autorizacao: 'Bearer jwt', url: URL, aoFalhar })
185
- );
186
- assert.equal(falhas.length, 1);
187
- });
188
-
189
- test('nao devolve promise, para ninguem conseguir esperar por ela', () => {
190
- const retorno = publicarEvento({
191
- tipo: 'meu_evento',
192
- autorizacao: 'Bearer jwt',
193
- url: URL,
194
- aoFalhar,
195
- });
196
-
197
- assert.equal(retorno, undefined);
198
- });
199
- });
200
-
201
- describe('publicarEventoDaRequisicao', () => {
202
- let fetchOriginal;
203
- let warnOriginal;
204
- let errorOriginal;
205
- let urlOriginal;
206
- let cookieOriginal;
207
- let chamadas;
208
-
209
- beforeEach(() => {
210
- fetchOriginal = global.fetch;
211
- warnOriginal = console.warn;
212
- errorOriginal = console.error;
213
- urlOriginal = process.env.MOTOR_EVENTOS_URL;
214
- cookieOriginal = process.env.MOTOR_EVENTOS_COOKIE_VISITANTE;
215
- chamadas = [];
216
- console.warn = () => {};
217
- console.error = () => {};
218
- process.env.MOTOR_EVENTOS_URL = URL;
219
- global.fetch = (url, opcoes) => {
220
- chamadas.push({ url, opcoes });
221
- return Promise.resolve({ ok: true, status: 201 });
222
- };
223
- });
224
-
225
- afterEach(() => {
226
- global.fetch = fetchOriginal;
227
- console.warn = warnOriginal;
228
- console.error = errorOriginal;
229
- for (const [chave, valor] of [
230
- ['MOTOR_EVENTOS_URL', urlOriginal],
231
- ['MOTOR_EVENTOS_COOKIE_VISITANTE', cookieOriginal],
232
- ]) {
233
- if (valor === undefined) delete process.env[chave];
234
- else process.env[chave] = valor;
235
- }
236
- });
237
-
238
- const corpoEnviado = () => JSON.parse(chamadas[0].opcoes.body);
239
-
240
- test('tira o token do header da requisicao', async () => {
241
- publicarEventoDaRequisicao(
242
- { headers: { authorization: 'Bearer jwt' }, body: {} },
243
- { tipo: 'meu_evento', payload: { campo: 'valor' } }
244
- );
245
- await esperarMicrotasks();
246
-
247
- assert.equal(chamadas[0].opcoes.headers.Authorization, 'Bearer jwt');
248
- assert.deepEqual(corpoEnviado().payload, { campo: 'valor' });
249
- });
250
-
251
- test('tira o visitante do cookie quando nao ha token', async () => {
252
- publicarEventoDaRequisicao(
253
- { headers: { cookie: 'trk=%7B%22id%22%3A%22visitante-1%22%7D' }, body: {} },
254
- { tipo: 'meu_evento', cookieVisitante: 'trk' }
255
- );
256
- await esperarMicrotasks();
257
-
258
- assert.equal(corpoEnviado().id_visitante, 'visitante-1');
259
- assert.equal(chamadas[0].opcoes.headers.Authorization, undefined);
260
- });
261
-
262
- test('o corpo tem precedencia sobre o cookie', async () => {
263
- publicarEventoDaRequisicao(
264
- {
265
- headers: { cookie: 'trk=%7B%22id%22%3A%22do-cookie%22%7D' },
266
- body: { id_visitante: 'do-corpo' },
267
- },
268
- { tipo: 'meu_evento', cookieVisitante: 'trk' }
269
- );
270
- await esperarMicrotasks();
271
-
272
- assert.equal(corpoEnviado().id_visitante, 'do-corpo');
273
- });
274
-
275
- test('cai na MOTOR_EVENTOS_COOKIE_VISITANTE quando o nome nao e passado', async () => {
276
- process.env.MOTOR_EVENTOS_COOKIE_VISITANTE = 'trk';
277
-
278
- publicarEventoDaRequisicao(
279
- { headers: { cookie: 'trk=visitante-2' }, body: {} },
280
- { tipo: 'meu_evento' }
281
- );
282
- await esperarMicrotasks();
283
-
284
- assert.equal(corpoEnviado().id_visitante, 'visitante-2');
285
- });
286
-
287
- test('nao publica quando a requisicao nao traz identidade nenhuma', async () => {
288
- delete process.env.MOTOR_EVENTOS_COOKIE_VISITANTE;
289
-
290
- publicarEventoDaRequisicao({ headers: {}, body: {} }, { tipo: 'meu_evento' });
291
- await esperarMicrotasks();
292
-
293
- assert.equal(chamadas.length, 0);
294
- });
295
-
296
- test('nao quebra com requisicao sem headers, sem corpo, ou sem argumento nenhum', () => {
297
- assert.doesNotThrow(() => publicarEventoDaRequisicao({}, { tipo: 'meu_evento' }));
298
- assert.doesNotThrow(() => publicarEventoDaRequisicao());
299
- });
300
-
301
- test('repassa as demais opcoes para o publicarEvento', async () => {
302
- publicarEventoDaRequisicao(
303
- { headers: { authorization: 'Bearer jwt' }, body: {} },
304
- { tipo: 'meu_evento', url: 'http://outro.interno/eventos' }
305
- );
306
- await esperarMicrotasks();
307
-
308
- assert.equal(chamadas[0].url, 'http://outro.interno/eventos');
309
- });
310
- });