Incident : lancer `pnpm test` (apps/api) truncait la vraie base de dev locale — `test-support/reset-db.ts`'s `resetDatabase()` fait un `TRUNCATE ... CASCADE` sur quasiment tout le schéma (dont `house`/`house_source`) avant *chaque* test, et `.env`/tests partageaient le même `DATABASE_URL` (un seul fichier `.env`, `NODE_ENV=test` ne changeait rien). Deux lancements du test suite cette session ont ainsi effacé le foyer, le compte et les activations de sources d'un utilisateur en train de tester l'app en local — perte réelle, aucune récupération possible (TRUNCATE, pas de sauvegarde). - `config/env.ts` charge désormais `.env.test` (pas `.env`) quand `NODE_ENV=test` — `.env.test` (local, non commité, comme `.env`) pointe vers une base Postgres séparée (`batchcooking_test`, même serveur/identifiants que la base de dev, juste une base différente). `.env.test.example` documente comment la créer. - `resetDatabase()` refuse maintenant de tourner si `NODE_ENV !== "test"` ou si `DATABASE_URL` ne contient pas "test" — garde-fou supplémentaire si `.env.test` est un jour absent/mal configuré, pour ne plus jamais reproduire cet incident même en cas d'erreur de configuration. - `.gitignore` autorise `.env.test.example` (déjà ignoré via `.env.*`, comme `.env.example` l'est déjà pour `.env`). Vérifié : snapshot de la base de dev (houses/house_sources/users) avant/ après un lancement complet de `pnpm test` — identique, base de dev intacte. 282 tests toujours au vert, contre la nouvelle base de test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
66 lines
3.5 KiB
TypeScript
66 lines
3.5 KiB
TypeScript
import dotenv from "dotenv";
|
|
import { z } from "zod";
|
|
|
|
// Loads `.env.test` instead of `.env` when running the test suite
|
|
// (NODE_ENV=test, set by `cross-env` in package.json's `test` script —
|
|
// already present in `process.env` by the time this module runs, since
|
|
// `cross-env` sets it before invoking node/tsx at all). Keeps
|
|
// `resetDatabase()` (test-support/reset-db.ts, which TRUNCATEs almost
|
|
// every table before each test) pointed at a dedicated test database,
|
|
// never whatever `pnpm dev` actually uses — running the test suite once
|
|
// already wiped a real local dev database this way (`.env`/`.env.test`
|
|
// sharing one `DATABASE_URL`), see `.env.test.example` for how to set the
|
|
// separate test database this now requires.
|
|
dotenv.config({ path: process.env.NODE_ENV === "test" ? ".env.test" : ".env" });
|
|
|
|
/**
|
|
* Schema for every environment variable the API reads. Parsing (below)
|
|
* fails fast at startup if something required is missing/invalid, instead
|
|
* of surfacing as a confusing runtime error later.
|
|
*/
|
|
const envSchema = z.object({
|
|
/** Runtime mode — also toggles test-only behavior (e.g. cheaper argon2 cost, see auth.service.ts). */
|
|
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
|
/** Port the HTTP server listens on. */
|
|
PORT: z.coerce.number().int().positive().default(3000),
|
|
/** Postgres connection string, consumed by Prisma. */
|
|
DATABASE_URL: z.string().url().optional(),
|
|
// Auth — no default on purpose, same reasoning as docker-compose.yml's
|
|
// POSTGRES_USER/PASSWORD: a secret must never have a working fallback
|
|
// baked into committed code.
|
|
/** Secret used to sign/verify session JWTs. Required, no default — see comment above. */
|
|
JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters"),
|
|
/** JWT expiry, in `jsonwebtoken`'s duration string format (e.g. "7d"). */
|
|
JWT_EXPIRES_IN: z.string().default("7d"),
|
|
/** Name of the httpOnly cookie carrying the session JWT. */
|
|
AUTH_COOKIE_NAME: z.string().default("session"),
|
|
/** Origin allowed by CORS — must match wherever apps/web is served from. */
|
|
CORS_ORIGIN: z.string().default("http://localhost:5173"),
|
|
/**
|
|
* Absolute path to the built frontend (`apps/web/dist`), to serve
|
|
* alongside the API. Optional, no default — only set inside the
|
|
* production Docker image (see Dockerfile); left unset in native dev
|
|
* (`pnpm dev:api`), where `pnpm dev:web`'s own Vite dev server serves
|
|
* the frontend instead.
|
|
*/
|
|
FRONTEND_DIST_DIR: z.string().optional(),
|
|
/**
|
|
* Overrides whether the session cookie gets the `Secure` attribute
|
|
* (HTTPS-only — see auth.routes.ts). Independent from NODE_ENV on
|
|
* purpose: NODE_ENV=production doesn't imply the deployment actually
|
|
* has TLS in front of it (e.g. an HTTP-only dev/staging instance), and
|
|
* a `Secure` cookie is silently never sent back by the browser over
|
|
* plain HTTP — every authenticated request 401s despite login
|
|
* succeeding, with no error to point at the cause. Unset (the default)
|
|
* falls back to NODE_ENV === "production", same as before this existed.
|
|
* Empty string counts as unset too, so `${COOKIE_SECURE:-}` in
|
|
* docker-compose.yml doesn't force it to `false` when not provided.
|
|
*/
|
|
COOKIE_SECURE: z
|
|
.string()
|
|
.optional()
|
|
.transform((value) => (value === undefined || value === "" ? undefined : value === "true")),
|
|
});
|
|
|
|
/** Parsed, validated environment — import this instead of reading `process.env` directly anywhere else. */
|
|
export const env = envSchema.parse(process.env);
|