O botão das ofertas rola até o contato com o tipo marcado e não quebra mais as âncoras; ofertas com o que você recebe e primeiro passo, logo depois da vitrine; bloco de IA fora da home; cartão largo da SEV no hero do celular; separador da faixa de clientes só entre itens; favicon de 13 para 1,8 KB; pré-carregamento só no hover e nunca de /#... fora da home; X do menu e cabeçalho opaco; WhatsApp com "o case"; /ia com 3 passos e modelo de relatório ilustrativo; Sobre alinhado ao currículo; CSP Report-Only com os domínios do Ads.
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import nextConfig from "../next.config";
|
|
|
|
/**
|
|
* Testes de caminhos-base de headers() em next.config.ts: o que muda entre produção e desenvolvimento.
|
|
* Produção ganha o HSTS e perde o 'unsafe-eval' da política de conteúdo (só o hot reload do dev precisa dele).
|
|
*/
|
|
|
|
async function siteHeaders() {
|
|
const rules = (await nextConfig.headers?.()) ?? [];
|
|
const site = rules.find(({ source }) => source === "/(.*)");
|
|
if (!site) throw new Error("Missing site-wide header rule");
|
|
return site.headers;
|
|
}
|
|
|
|
const header = (headers: { key: string; value: string }[], key: string) =>
|
|
headers.find((item) => item.key === key)?.value;
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe("next.config headers()", () => {
|
|
it("path: production -> HSTS is sent and the policy has no 'unsafe-eval'", async () => {
|
|
jest.replaceProperty(process.env, "NODE_ENV", "production");
|
|
|
|
const headers = await siteHeaders();
|
|
|
|
expect(header(headers, "Strict-Transport-Security")).toBe(
|
|
"max-age=31536000; includeSubDomains; preload",
|
|
);
|
|
expect(
|
|
header(headers, "Content-Security-Policy-Report-Only"),
|
|
).not.toContain("'unsafe-eval'");
|
|
});
|
|
|
|
it("path: development or test -> no HSTS, and the policy allows 'unsafe-eval' for the dev server", async () => {
|
|
jest.replaceProperty(process.env, "NODE_ENV", "development");
|
|
|
|
const headers = await siteHeaders();
|
|
|
|
expect(header(headers, "Strict-Transport-Security")).toBeUndefined();
|
|
expect(header(headers, "Content-Security-Policy-Report-Only")).toContain(
|
|
"'unsafe-eval'",
|
|
);
|
|
});
|
|
|
|
it("path: any environment -> the policy (still Report-Only) allows the domains the Google Ads tag uses after consent, in connect-src and img-src", async () => {
|
|
const headers = await siteHeaders();
|
|
const policy = header(headers, "Content-Security-Policy-Report-Only") ?? "";
|
|
const directive = (name: string) =>
|
|
policy.split("; ").find((part) => part.startsWith(`${name} `)) ?? "";
|
|
|
|
for (const name of ["connect-src", "img-src"]) {
|
|
for (const host of [
|
|
"https://pagead2.googlesyndication.com",
|
|
"https://googleads.g.doubleclick.net",
|
|
"https://www.googleadservices.com",
|
|
]) {
|
|
expect(directive(name)).toContain(host);
|
|
}
|
|
// o que já estava liberado segue liberado
|
|
expect(directive(name)).toContain("https://*.google-analytics.com");
|
|
}
|
|
// nada de curinga aberto e nada enforçado: a política continua só em Report-Only
|
|
expect(policy).not.toMatch(/\* |https:(?!\/)/);
|
|
expect(header(headers, "Content-Security-Policy")).toBeUndefined();
|
|
});
|
|
|
|
it("path: any environment -> the video rule keeps the long cache", async () => {
|
|
const rules = (await nextConfig.headers?.()) ?? [];
|
|
const videos = rules.find(({ source }) => source === "/videos/:path*");
|
|
|
|
expect(videos?.headers).toEqual([
|
|
{
|
|
key: "Cache-Control",
|
|
value: "public, max-age=86400, stale-while-revalidate=604800",
|
|
},
|
|
]);
|
|
});
|
|
});
|