Merge PR #914: test(branding): a catraca passa a pegar host de terceiro, não só a marca (@webtecnica)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pessoa
2026-09-16 16:30:45 -03:00
co-authored by Claude Opus 5
2 changed files with 473 additions and 6 deletions
+18
View File
@@ -0,0 +1,18 @@
---
impacto: nada_mudou
secao: corrigido
titulo: O domínio de quem manda o PR não viaja mais dentro da imagem
---
Nada muda na sua VPS: nenhum endereço de terceiro foi encontrado no código que
embarca hoje, e nada foi trocado em produção. O que muda é o portão do
repositório. A catraca que já barrava o nome antigo do produto — "DeskcommCRM"
escrito na tela de quem instalou com a marca dele — passou a barrar também
domínio de terceiro: site pessoal, endereço de teste ou visão de alguém
enterrado numa parte do código que vai para a imagem. Quando isso acontece, o PR
é reprovado com o endereço e o arquivo na mensagem. Foi por esse buraco que a
identificação enviada à OpenRouter levava o endereço pessoal de quem escreveu o
PR: o consumo de cada instalação ficava creditado a um site que não é o seu nem
o nosso.
Crédito: @webtecnica.
+455 -6
View File
@@ -366,11 +366,40 @@ const RAIZES_VARRIDAS = ["app", "components", "hooks", "lib", "workers"] as cons
* procurar `//` em qualquer posição descartaria `"https://deskcomm.app"`, que é
* exatamente um vazamento de verdade.
*/
function marcasNoTexto(fonte: string): string[] {
const achadas: string[] = [];
/**
* As linhas de um fonte que CONTAM — a regra de comentário em um lugar só.
*
* Linha que ABRE com `//`, `*` ou `/*` é comentário e não conta: comentário não
* chega ao usuário, e contá-lo encheria a lista de entradas inertes até ninguém
* mais ler as que importam. O teste olha só o início da linha DE PROPÓSITO —
* procurar `//` em qualquer posição descartaria `"https://deskcomm.app"`, que é
* exatamente um vazamento de verdade.
*
* NÃO se reaproveita onde a fonte não é TypeScript: o comentário de HTML
* (`<!-- -->`) atravessa linhas e o de TOML abre com `#`. A varredura de
* `supabase/templates/*.html` e `config.toml` tem as próprias funções de
* limpeza, logo abaixo, e o porquê está escrito lá.
*/
function linhasQueContam(fonte: string): string[] {
const uteis: string[] = [];
for (const linha of fonte.split("\n")) {
const inicio = linha.trimStart();
if (inicio.startsWith("//") || inicio.startsWith("*") || inicio.startsWith("/*")) continue;
uteis.push(linha);
}
return uteis;
}
/**
* Extrai as marcas de um texto, uma por ocorrência.
*
* Casa `deskcomm` em qualquer caixa e leva junto o identificador inteiro em volta
* (`x-deskcomm-signature`, `support@deskcomm.com.br`), porque é o identificador —
* não a palavra solta — que distingue contrato de fio de vazamento de marca.
*/
function marcasNoTexto(fonte: string): string[] {
const achadas: string[] = [];
for (const linha of linhasQueContam(fonte)) {
for (const casada of linha.matchAll(/[\w@.-]*deskcomm[\w@.-]*/gi)) {
// Pontuação encostada (o ponto final de "no DeskcommCRM.") não faz parte
// do identificador e faria a lista mudar por causa de uma vírgula.
@@ -380,13 +409,109 @@ function marcasNoTexto(fonte: string): string[] {
return achadas.sort();
}
function arquivosVarridos(dir: string): string[] {
/**
* Host dentro de URL. Exigir rótulo final de LETRAS (`\.[a-z]{2,}`) é o que
* dispensa, de uma vez e sem linha de allowlist nenhuma, `http://.../`,
* `http://localhost:3000`, `http://waha:3000` e TODO IP literal — `127.0.0.1`,
* `10.0.0.5`, `169.254.1.1`: nenhum deles termina em TLD alfabético.
*/
const HOST_EM_URL = /https?:\/\/([a-z0-9][a-z0-9.-]*\.[a-z]{2,})/gi;
/**
* Host NU, e só quando é o CONTEÚDO de uma string (`"meet.google.com"`).
*
* Fora de aspas ele é identificador, e a forma "em qualquer contexto" foi medida
* antes de ser descartada: casa `logger.info(` (37 linhas), `auth.organization_id`
* e `message.received` — 339 linhas e 160 "hosts" na superfície que embarca,
* quase todos falsos. A lista de TLDs é a parte heurística desta forma, e ela é
* explícita de propósito: um `[a-z]{2,}` genérico é o que produz os 160.
*
* O `(?![a-z0-9_-])` depois do TLD é carregador, e as duas metades dele vêm de
* medição, não de gosto:
* - sem ele, `auth.organization_id` casa como `auth.org`;
* - sem o `_`, os nomes de evento de auditoria casam em bloco — medido na
* `main`: `onboarding.ai_configured`, `ai.org_memory_published`,
* `channel.ai_access_updated`, `conversation.ai_paused`,
* `lgpd.store_redact_received` e `env.APP_NAME` (6 falsos, todos com `_`
* logo depois do "TLD"). `_` não existe em nome de host, então barrá-lo não
* esconde host nenhum.
*/
const HOST_NU_EM_STRING =
/["'`](?:https?:\/\/)?((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:com\.br|com|net|org|io|app|dev|ai|co|cloud|br|me|tv|edu|gov|info|xyz|site|online|shop|store))(?![a-z0-9_-])/gi;
/**
* Host que não é terceiro porque não resolve para ninguém: TLD reservado para
* documentação e teste (RFC 6761) e o próprio `example.com` com os irmãos
* (RFC 2606). `exemplo`/`ejemplo` NÃO entram aqui de propósito — `mi-gateway.ejemplo.com`
* está no código que embarca como amostra de campo, e amostra se DECLARA
* (categoria `AMOSTRA`), não se ignora em silêncio.
*/
const TLDS_RESERVADOS = /\.(test|invalid|local|localhost|example)$/i;
const EXAMPLE_RFC2606 = /(^|\.)example\.(com|net|org)$/i;
/**
* Extrai os hosts de terceiro de um texto — distintos, em minúsculas, em ordem.
*
* Distintos (e não uma por ocorrência, como `marcasNoTexto`) porque a lista de
* hosts declarados é por HOST, não por arquivo: o segundo call site de um
* endpoint já declarado não pede linha nova, e é isso que impede a catraca de
* virar churn de allowlist a cada arquivo que passa a chamar `api.openai.com`
* (medido: ele aparece em 5 arquivos, `graph.facebook.com` em 6).
*
* Duas formas, as duas medidas na calibração da #287: URL (`HOST_EM_URL`) e host
* nu em string (`HOST_NU_EM_STRING`). A terceira — host nu em QUALQUER contexto —
* está medida e rejeitada acima.
*/
function hostsNoTexto(fonte: string): string[] {
const achados = new Set<string>();
for (const linha of linhasQueContam(fonte)) {
for (const m of linha.matchAll(HOST_EM_URL)) {
const host = m[1];
if (host) achados.add(host.toLowerCase());
}
for (const m of linha.matchAll(HOST_NU_EM_STRING)) {
const host = m[1];
if (host) achados.add(host.toLowerCase());
}
}
return [...achados].filter((h) => !TLDS_RESERVADOS.test(h) && !EXAMPLE_RFC2606.test(h)).sort();
}
/**
* Os arquivos varridos de uma raiz.
*
* As duas opções existem porque as varreduras divergem em DOIS pontos, e cada
* divergência tem motivo medido — não é preferência:
*
* - `app/design` fica FORA da catraca de marca (lá o nome do produto é o ASSUNTO
* da página) e DENTRO da de host, cujo critério é outro: "embarca na imagem".
* `app/design` embarca. Medido: 0 hosts lá hoje, então entrar custa zero.
* - os `*.test.ts(x)` ficam FORA da catraca de host: fixture não embarca (o
* `Dockerfile` copia `.next/standalone`, `.next/static` e `public/`), e com
* eles dentro os hosts a declarar subiam de 21 para 61 — `localhost` em 44
* arquivos, `crm.exemplo.com` em 12 linhas de fixture. Allowlist de fixture
* afoga o sinal que a catraca existe para dar.
*/
function arquivosVarridos(
dir: string,
opcoes: { comDesign?: boolean; comTeste?: boolean } = {},
): string[] {
// `comTeste` default TRUE de propósito: a catraca de marca conta os arquivos
// de teste (três deles estão em MARCA_CONGELADA como dívida declarada). Quem
// quer fixture fora — a de host — passa `comTeste: false` explicitamente.
const comDesign = opcoes.comDesign ?? false;
const comTeste = opcoes.comTeste ?? true;
const alvos: string[] = [];
for (const entrada of fs.readdirSync(path.join(RAIZ, dir), { withFileTypes: true })) {
const rel = path.posix.join(dir, entrada.name);
if (rel.startsWith("app/design")) continue;
if (entrada.isDirectory()) alvos.push(...arquivosVarridos(rel));
else if (rel.endsWith(".ts") || rel.endsWith(".tsx")) alvos.push(rel);
if (!comDesign && rel.startsWith("app/design")) continue;
if (entrada.isDirectory()) {
alvos.push(...arquivosVarridos(rel, opcoes));
continue;
}
if (!rel.endsWith(".ts") && !rel.endsWith(".tsx")) continue;
if (!comTeste && /\.(test|spec)\.tsx?$/.test(rel)) continue;
alvos.push(rel);
}
return alvos;
}
@@ -630,3 +755,327 @@ describe("catraca de marca no que o GoTrue renderiza", () => {
expect(encontradoAqui.has("supabase/templates/recovery.html")).toBe(false);
});
});
/**
* A TERCEIRA varredura: host de terceiro.
*
* A catraca acima é cega para URL sem a marca. O caso medido é o #266: um
* `HTTP-Referer` com o domínio pessoal do contribuidor foi parar na chamada de
* TODO self-hoster, e a varredura da época só olhava `deskcomm` — o `X-Title`
* ficava coberto pela marca, o domínio sozinho passava verde. Host de terceiro
* no código que embarca é a mesma falha por outro nome: o domínio de alguém
* viaja na imagem que o cliente instala, e ninguém vê.
*
* Tudo aqui foi MEDIDO na `main` antes de virar régua — catraca nova se calibra
* antes de virar catraca, senão ela nasce ruidosa e o time aprende a ignorá-la:
*
* - superfície: `app|components|hooks|lib|workers`, o que o `Dockerfile` copia
* para a imagem (`.next/standalone`, `.next/static`, `public/`) — a MESMA da
* catraca de marca, mais `app/design` (embarca) e menos os `*.test.ts(x)`
* (fixture não embarca). 1717 arquivos.
* - régua crua sobre a `main`: 21 hosts, NENHUM vazamento — todos endpoint de
* fornecedor, painel de fornecedor, amostra de campo ou identificador de fio.
* Cada um está declarado abaixo com categoria e motivo, e é o julgamento
* deles que esta catraca passa a defender.
* - o que a régua NÃO pega, de propósito e com medição: host nu FORA de aspas
* (339 linhas e 160 "hosts" na superfície, quase todos identificador
* pontuado — `logger.info(`, `auth.organization_id`, `message.received`) e
* host com TLD fora da lista explícita de `HOST_NU_EM_STRING`. As duas são a
* fronteira conhecida desta catraca; a alternativa foi medida e é pior.
* - lista por HOST, não por arquivo: o segundo call site de um endpoint já
* declarado não pede linha nova (`api.openai.com` aparece em 5 arquivos,
* `graph.facebook.com` em 6). `FORNECEDOR` pode crescer — provider novo é
* provider novo. `CONSOLE`, `AMOSTRA`, `PLATAFORMA` e `PROTOCOLO` são
* conjunto FECHADO fixado por nome, porque é nessas que a pressa tentaria
* declarar um vazamento para seguir em frente.
*/
type CategoriaDeHost =
/** Endpoint do fornecedor: PARA ONDE o código fala. Pode crescer. */
| "FORNECEDOR"
/** Painel/documentação do fornecedor: onde o usuário busca a credencial DELE. */
| "CONSOLE"
/** Amostra de formato em campo de formulário. Chega à tela — por isso se declara. */
| "AMOSTRA"
/** Host de plataforma ACEITO na entrada (validação), não destino de chamada. */
| "PLATAFORMA"
/** Identificador de fio que gravamos; quem reconhece é código de fora. */
| "PROTOCOLO";
type EntradaDeHost = { categoria: CategoriaDeHost; motivo: string };
const HOSTS_DECLARADOS: Record<string, EntradaDeHost> = {
// ── destino de chamada: o código fala com eles, sempre foi assim ──────────
"api.openai.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint da API da OpenAI (embeddings da busca e transcrição de áudio). É o destino do request: trocar pelo domínio do revendedor faria a chamada não chegar a lugar nenhum.",
},
"api.anthropic.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint da API da Anthropic (contagem de tokens e a prova de credencial da tela de configuração). Mesma razão: é o destino, não texto de interface.",
},
"openrouter.ai": {
categoria: "FORNECEDOR",
motivo:
"endpoint da OpenRouter nos três caminhos que falam com ela (runtime, catálogo de modelos e prova de crédito). O `HTTP-Referer` da atribuição NÃO mora aqui — sai de env (OPENROUTER_APP_URL), e quem o defende é tests/unit/openrouter-atribuicao.test.ts.",
},
"generativelanguage.googleapis.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint da API Gemini (visão e tradução). Domínio do fornecedor, sem alternativa que não seja proxy nosso.",
},
"graph.facebook.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint da Graph API do WhatsApp Cloud — 6 arquivos: envio de template, sincronização de modelos, validação de credencial, conversões e insights. É contrato da Meta, não escolha nossa.",
},
"www.googleapis.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint das APIs do Google que a agenda usa (calendar/v3, oauth2/v4, userinfo). O projeto é do cliente; o domínio é do fornecedor.",
},
"oauth2.googleapis.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint de token do OAuth do Google — o par de `accounts.google.com` no fluxo de autorização da agenda.",
},
"accounts.google.com": {
categoria: "FORNECEDOR",
motivo:
"tela de consentimento do OAuth do Google: é para lá que o usuário é REDIRECIONADO para autorizar a agenda. Endereço do fornecedor por definição.",
},
"api.tiendanube.com": {
categoria: "FORNECEDOR",
motivo: "endpoint da API da Nuvemshop/Tiendanube (ordens e catálogo do e-commerce do cliente).",
},
"www.tiendanube.com": {
categoria: "FORNECEDOR",
motivo:
"base de autorização do OAuth da Nuvemshop — para onde o lojista é mandado autorizar o aplicativo. Sem ela, a integração não conecta.",
},
"zernio.com": {
categoria: "FORNECEDOR",
motivo:
"endpoint padrão do adapter do canal de mensagens, com override por ZERNIO_API_BASE_URL. Fixo de propósito: instalação que não configura nada tem de funcionar.",
},
// ── painel do fornecedor: texto de tela apontando para o endereço DELE ────
"platform.openai.com": {
categoria: "CONSOLE",
motivo:
"painel onde o usuário gera a PRÓPRIA chave da OpenAI. O endereço é do fornecedor e é a informação que a tela tem de dar — mandar para outro lugar seria pior.",
},
"console.anthropic.com": {
categoria: "CONSOLE",
motivo: "painel de chaves da Anthropic. Mesmo caso: é de onde a credencial do usuário sai.",
},
"aistudio.google.com": {
categoria: "CONSOLE",
motivo: "Google AI Studio — onde o usuário cria a chave do Gemini.",
},
"partners.tiendanube.com": {
categoria: "CONSOLE",
motivo:
"portal de parceiros da Nuvemshop, onde o operador registra o aplicativo e pega client id e secret. Endereço da plataforma, não nosso.",
},
// ── amostra de formato: mostra o que digitar, não é destino ──────────────
"meusistema.com": {
categoria: "AMOSTRA",
motivo:
"placeholder do campo de URL do webhook de saída: mostra o FORMATO do que digitar. A doutrina já trata endereço de rede em tela como exemplo técnico (tests/unit/i18n-espanhol-cobre-a-tela.test.ts).",
},
"tusitio.com": {
categoria: "AMOSTRA",
motivo:
"placeholder do campo de URL de redirecionamento do webhook: mesma natureza — amostra de formato, não destino que o produto busca.",
},
"mi-gateway.ejemplo.com": {
categoria: "AMOSTRA",
motivo:
"placeholder do campo de base URL de gateway OpenAI-compatible na tela de provedores: amostra do formato aceito.",
},
"000000000000-xxxxxxxx.apps.googleusercontent.com": {
categoria: "AMOSTRA",
motivo:
"placeholder do campo de client id do Google: exibe o FORMATO do identificador (e o host que ele carrega) para quem vai criar a credencial no console.",
},
// ── entrada conferida e identificador de fio ─────────────────────────────
"meet.google.com": {
categoria: "PLATAFORMA",
motivo:
"host do Google Meet aceito na validação do link de reunião (`meetVideoUrl`): é entrada que o produto CONFERE, não endereço que ele busca. Sem a linha, qualquer host passaria por link de reunião.",
},
"deskcomm.app": {
categoria: "PROTOCOLO",
motivo:
"sufixo do iCalUID gravado no Google Calendar do cliente (lib/agenda/google/evento.ts). Identificador de fio que reconhecemos meses depois — já congelado como PROTOCOLO pela catraca de marca.",
},
};
describe("catraca de host de terceiro no código que embarca", () => {
const alvos = RAIZES_VARRIDAS.flatMap((raiz) =>
arquivosVarridos(raiz, { comDesign: true, comTeste: false }),
);
/** host → arquivos onde aparece, para a falha apontar onde ele mora. */
const encontrados = new Map<string, string[]>();
for (const arquivo of alvos) {
for (const host of hostsNoTexto(fs.readFileSync(path.join(RAIZ, arquivo), "utf8"))) {
encontrados.set(host, [...(encontrados.get(host) ?? []), arquivo]);
}
}
it("a varredura alcança o código que embarca — senão o resto não prova nada", () => {
expect(alvos.length).toBeGreaterThan(800);
expect(alvos.filter((f) => f.endsWith(".tsx")).length).toBeGreaterThan(200);
expect(alvos.filter((f) => /\.(test|spec)\.tsx?$/.test(f))).toEqual([]);
// Canário: `api.openai.com` está no código desde antes desta catraca. Se o
// extrator quebrar (regex, regra de comentário, forma da string), o conjunto
// esvazia, TODOS os casos abaixo passam e a catraca vira decoração — foi
// assim que a varredura antiga ficava verde enquanto o domínio vazava.
expect(
encontrados.has("api.openai.com"),
"canário: o extrator parou de achar o que sempre achou",
).toBe(true);
});
it("pega host de terceiro no formato do #266 — o caso que a régua antiga deixava passar", () => {
// O contrafactual da issue, com o `X-Title` fora de propósito: a catraca de
// marca pega o X-Title (é a marca) e não pegava o domínio sozinho.
expect(
hostsNoTexto('headers: { "HTTP-Referer": "https://crm.do-contribuidor.com.br" }'),
).toEqual(["crm.do-contribuidor.com.br"]);
expect(hostsNoTexto('const BASE = "https://api.do-contribuidor.com.br/v1";')).toEqual([
"api.do-contribuidor.com.br",
]);
expect(hostsNoTexto("const BASE = `https://api.do-contribuidor.com.br/v1`;")).toEqual([
"api.do-contribuidor.com.br",
]);
});
it("pega host NU quando ele é o conteúdo de uma string — e não identificador pontuado", () => {
expect(hostsNoTexto('url.hostname === "meet.google.com"')).toEqual(["meet.google.com"]);
expect(hostsNoTexto('placeholder="000000000000-xxxxxxxx.apps.googleusercontent.com"')).toEqual([
"000000000000-xxxxxxxx.apps.googleusercontent.com",
]);
// Os falsos medidos da forma "em qualquer contexto" — é por causa deles que
// o host nu só vale dentro de aspas, e é o `(?![a-z0-9_-])` que impede
// `auth.organization_id` de casar como `auth.org`.
expect(hostsNoTexto("logger.info(evento);")).toEqual([]);
expect(hostsNoTexto('audit("auth.organization_id", { org });')).toEqual([]);
expect(hostsNoTexto('emit("message.received", payload);')).toEqual([]);
expect(hostsNoTexto('localStorage.getItem("deskcomm.show_ai_citations")')).toEqual([]);
// Os 6 falsos que a PRIMEIRA versão da régua produziu na `main`: nomes de
// evento de auditoria cujo prefixo termina num "TLD" (`ai`, `org`, `app`,
// `store`) seguido de `_`. Nenhum deles é host, e é o `_` no lookahead que
// os separa de um host de verdade.
expect(hostsNoTexto('action: "onboarding.ai_configured",')).toEqual([]);
expect(hostsNoTexto('action: "ai.org_memory_published",')).toEqual([]);
expect(hostsNoTexto('action: "channel.ai_access_updated",')).toEqual([]);
expect(hostsNoTexto('action: "conversation.ai_paused",')).toEqual([]);
expect(hostsNoTexto('action: "lgpd.store_redact_received",')).toEqual([]);
expect(hostsNoTexto("`env.APP_NAME` cru")).toEqual([]);
});
it("host que não resolve — loopback, IP, serviço do Docker, sentinela — não pede linha", () => {
expect(hostsNoTexto('const u = "http://localhost:3000/api";')).toEqual([]);
expect(hostsNoTexto('fetch("http://waha:3000/api");')).toEqual([]);
expect(hostsNoTexto('const s = "http://127.0.0.1:54321";')).toEqual([]);
expect(hostsNoTexto('const s = "https://10.0.0.5/x";')).toEqual([]);
expect(hostsNoTexto('const s = "https://169.254.169.254/latest/meta-data";')).toEqual([]);
expect(hostsNoTexto('const s = "http://.../";')).toEqual([]);
expect(hostsNoTexto('const s = "https://crm.exemplo.test";')).toEqual([]);
expect(hostsNoTexto('const s = "https://x.example.com/v1";')).toEqual([]);
});
it("não conta host em comentário, e não confunde o `//` da URL com comentário", () => {
expect(hostsNoTexto(" // o endpoint é https://api.openai.com/v1")).toEqual([]);
expect(hostsNoTexto(" * https://api.openai.com/v1/docs")).toEqual([]);
expect(hostsNoTexto('const u = "https://api.openai.com/v1";')).toEqual(["api.openai.com"]);
});
it("nenhum host de terceiro fora da lista declarada", () => {
const novos = [...encontrados.keys()].filter((h) => !(h in HOSTS_DECLARADOS));
expect(
novos,
"Host de terceiro hardcoded no código que embarca (a imagem que o cliente instala).\n" +
"Se é endereço do fornecedor (o código FALA com ele) ou painel dele (o usuário busca a\n" +
"credencial lá), declare em HOSTS_DECLARADOS com categoria e motivo. Se é amostra de\n" +
"formato de campo, declare como AMOSTRA — ela chega à tela, então se declara em vez de\n" +
"se ignorar. E se é o domínio de alguém — o seu, o de um contribuidor, o do cliente de\n" +
"vocês, o de um serviço de teste —, tire do código: foi exatamente o #266.\n\n" +
novos
.map((h) => {
const onde = encontrados.get(h) ?? [];
const lista = onde.slice(0, 4).join(", ") + (onde.length > 4 ? ", …" : "");
return ` ${h} → ${lista}`;
})
.join("\n"),
).toEqual([]);
});
it("a lista não guarda host que saiu do código", () => {
const obsoletos = Object.keys(HOSTS_DECLARADOS).filter((h) => !encontrados.has(h));
expect(
obsoletos,
"Estes hosts não aparecem mais no código que embarca — apague a linha de HOSTS_DECLARADOS:\n" +
obsoletos.map((h) => ` ${h}`).join("\n"),
).toEqual([]);
});
it("toda entrada declara categoria válida e explica o porquê", () => {
const categorias: CategoriaDeHost[] = [
"FORNECEDOR",
"CONSOLE",
"AMOSTRA",
"PLATAFORMA",
"PROTOCOLO",
];
for (const [host, entrada] of Object.entries(HOSTS_DECLARADOS)) {
expect(categorias, `${host}: categoria desconhecida`).toContain(entrada.categoria);
expect(
entrada.motivo.length,
`${host}: motivo curto demais para servir de rastro`,
).toBeGreaterThan(40);
}
});
it("só FORNECEDOR pode crescer — o resto é conjunto fechado", () => {
// A pressa descreveria o vazamento como AMOSTRA (ou chamaria um domínio
// pessoal de CONSOLE) e seguiria em frente. Estas categorias não são
// destino de chamada, então crescem só por decisão explícita: a lista
// abaixo é fixada por NOME, e mexer nela é editar teste, no diff.
const fechadas = Object.entries(HOSTS_DECLARADOS)
.filter(([, entrada]) => entrada.categoria !== "FORNECEDOR")
.map(([host]) => host)
.sort();
expect(
fechadas,
"Categoria fechada ganhou host novo. Se o código FALA com ele, é FORNECEDOR; se não, " +
"o crescimento tem de ser uma decisão escrita aqui — não mais uma linha na lista.",
).toEqual([
"000000000000-xxxxxxxx.apps.googleusercontent.com",
"aistudio.google.com",
"console.anthropic.com",
"deskcomm.app",
"meet.google.com",
"meusistema.com",
"mi-gateway.ejemplo.com",
"partners.tiendanube.com",
"platform.openai.com",
"tusitio.com",
]);
});
it("o filtro de fixture e a fronteira do app/design funcionam", () => {
const comTeste = arquivosVarridos("hooks", { comTeste: true });
const semTeste = arquivosVarridos("hooks", { comTeste: false });
expect(semTeste.length).toBeGreaterThan(0);
expect(comTeste.length).toBeGreaterThan(semTeste.length);
expect(semTeste.filter((f) => /\.(test|spec)\.tsx?$/.test(f))).toEqual([]);
// `app/design` entra na varredura de host (embarca) e fica fora da de marca.
expect(
arquivosVarridos("app", { comDesign: true }).some((f) => f.startsWith("app/design")),
).toBe(true);
expect(arquivosVarridos("app").some((f) => f.startsWith("app/design"))).toBe(false);
});
});