98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
type TestWebConfig = {
|
|
nodeEnv: string;
|
|
sessionSecret: string;
|
|
routing: {
|
|
appBaseDomain: string;
|
|
protocol: "http" | "https";
|
|
};
|
|
};
|
|
|
|
const testWebConfig: TestWebConfig = {
|
|
nodeEnv: "production",
|
|
sessionSecret: "production-session-secret-with-enough-entropy",
|
|
routing: {
|
|
appBaseDomain: "example.com",
|
|
protocol: "https",
|
|
},
|
|
};
|
|
|
|
vi.doMock("@toolctrl/config", async () => {
|
|
const actual = await vi.importActual<typeof import("@toolctrl/config")>("@toolctrl/config");
|
|
return {
|
|
...actual,
|
|
loadRequiredWebConfig: () => testWebConfig,
|
|
};
|
|
});
|
|
|
|
async function importI18nextServerWithConfig(config: typeof testWebConfig) {
|
|
Object.assign(testWebConfig, config);
|
|
vi.resetModules();
|
|
return await import("./i18next.server");
|
|
}
|
|
|
|
describe("i18n cookie policy", () => {
|
|
it("resolves locale files from the workspace path when running from a bundled server file", async () => {
|
|
const { resolveServerLocalePath } = await importI18nextServerWithConfig(testWebConfig);
|
|
const resolved = resolveServerLocalePath({
|
|
moduleUrl: "file:///myapp/apps/web/build/server/assets/server-build.js",
|
|
cwd: "/myapp",
|
|
exists: (candidate) => candidate === "/myapp/apps/web/public/locales",
|
|
});
|
|
|
|
expect(resolved).toBe("/myapp/apps/web/public/locales");
|
|
});
|
|
|
|
it("keeps source-relative locale resolution for local source execution", async () => {
|
|
const { resolveServerLocalePath } = await importI18nextServerWithConfig(testWebConfig);
|
|
const resolved = resolveServerLocalePath({
|
|
moduleUrl: "file:///repo/apps/web/app/locale/i18next.server.ts",
|
|
cwd: "/repo",
|
|
exists: (candidate) => candidate === "/repo/apps/web/public/locales",
|
|
});
|
|
|
|
expect(resolved).toBe("/repo/apps/web/public/locales");
|
|
});
|
|
|
|
it("serializes hardened production i18n cookie attributes without exposing the session secret", async () => {
|
|
const productionSecret = "production-session-secret-with-enough-entropy";
|
|
const { i18nCookie } = await importI18nextServerWithConfig({
|
|
nodeEnv: "production",
|
|
sessionSecret: productionSecret,
|
|
routing: {
|
|
appBaseDomain: "example.com",
|
|
protocol: "https",
|
|
},
|
|
});
|
|
|
|
const header = await i18nCookie.serialize("de");
|
|
|
|
expect(header).toContain("_i18n=");
|
|
expect(header).toContain("Domain=example.com");
|
|
expect(header).toContain("SameSite=Lax");
|
|
expect(header).toContain("Secure");
|
|
expect(header).toContain("HttpOnly");
|
|
expect(header).toContain("Path=/");
|
|
expect(header).not.toContain(productionSecret);
|
|
});
|
|
|
|
it("keeps local i18n cookies domainless", async () => {
|
|
const { i18nCookie } = await importI18nextServerWithConfig({
|
|
nodeEnv: "test",
|
|
sessionSecret: "local-session-secret-with-enough-entropy",
|
|
routing: {
|
|
appBaseDomain: "localhost",
|
|
protocol: "http",
|
|
},
|
|
});
|
|
|
|
const header = await i18nCookie.serialize("de");
|
|
|
|
expect(header).toContain("_i18n=");
|
|
expect(header).not.toContain("Domain=");
|
|
expect(header).not.toContain("Secure");
|
|
});
|
|
});
|