mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
bun loads a plain `.env` into process.env before anything runs, and tests/helpers/env.ts read that as a deliberate export, so it beat `.env.test` outright. `scripts/test.sh api` therefore pointed the api suites at whatever deployment `.env` describes: `prisma db push --accept-data-loss` for the schema, then a truncate of every table between tests. The e2e half was worse, because playwright.config.ts built and started the app with that DATABASE_URL and those R2 credentials, then wrote fixtures into it. CI never saw any of this: a runner has no `.env`. Three changes, in order of what each one catches: - helpers/dev-env.ts drops the values bun copied out of a development env file, leaving `.env.test` to fill them. Only values that match the file character-for-character go, so a real export still wins and the per-suite databases of a parallel api run keep working. - helpers/test-database.ts refuses a DATABASE_URL whose database name is not marked as a test one, at the single point every path into the setup passes through. This is the backstop, not the fix. - playwright.config.ts blanks the variables a development env file defines and the config does not. Dropping them from process.env is not enough there: `next build` and `next start` run @next/env themselves and read the files again. That is also why a local e2e run could not build at all (a set DISABLE_RATE_LIMIT throws in lib/rate-limit.ts under NODE_ENV=production) and why auth.spec.ts failed on a machine with SMTP configured. `scripts/test.sh` now creates `.env.test` from the committed example instead of asking for a one-line copy, so the guard above is something nobody has to meet.
121 lines
4.5 KiB
TypeScript
121 lines
4.5 KiB
TypeScript
// Locating the checkout, and undoing bun's automatic `.env` load.
|
|
//
|
|
// Split out of helpers/env.ts so it can be imported without side effects.
|
|
// Importing that module loads all of `.env.test` into process.env, which
|
|
// playwright.config.ts must not do: the file carries DISABLE_RATE_LIMIT for the
|
|
// api suites, and `next build` runs in production mode, where lib/rate-limit.ts
|
|
// refuses to start with that set.
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { parse as parseDotenv } from 'dotenv';
|
|
|
|
/**
|
|
* Walks up from the working directory until it finds the checkout.
|
|
*
|
|
* This deliberately avoids `import.meta.url`, which would be the obvious way to
|
|
* resolve a path relative to this file: Playwright transpiles TypeScript to
|
|
* CommonJS unless package.json declares `"type": "module"`, and in CommonJS
|
|
* `import.meta` is a *syntax* error, so the e2e suite could not import this
|
|
* module at all. `__dirname` has the mirror-image problem under Vitest's ESM.
|
|
*
|
|
* The marker is prisma/schema.prisma as well as package.json, so a stray
|
|
* package.json inside node_modules cannot be mistaken for the checkout.
|
|
*/
|
|
function findRepoRoot(): string {
|
|
let current = path.resolve(process.cwd());
|
|
|
|
for (;;) {
|
|
if (
|
|
fs.existsSync(path.join(current, 'package.json')) &&
|
|
fs.existsSync(path.join(current, 'prisma', 'schema.prisma'))
|
|
) {
|
|
return current;
|
|
}
|
|
|
|
const parent = path.dirname(current);
|
|
if (parent === current) {
|
|
throw new Error(
|
|
`Could not locate the OpenFrame checkout from ${process.cwd()}: no ancestor ` +
|
|
'directory holds both package.json and prisma/schema.prisma. Run the test ' +
|
|
'suites from the repository root.'
|
|
);
|
|
}
|
|
current = parent;
|
|
}
|
|
}
|
|
|
|
export const REPO_ROOT = findRepoRoot();
|
|
|
|
export const TEST_ENV_PATH = path.join(REPO_ROOT, '.env.test');
|
|
|
|
/**
|
|
* The env files a developer machine has and CI does not.
|
|
*
|
|
* bun autoloads these, and so does @next/env inside `next build` and
|
|
* `next start`, which is why both halves of the problem below need the same
|
|
* list. `.env.test` is deliberately absent: that one is the test configuration.
|
|
*/
|
|
const DEV_ENV_FILES = ['.env', '.env.local', '.env.production', '.env.production.local'];
|
|
|
|
function readDevEnv(): Record<string, string> {
|
|
const merged: Record<string, string> = {};
|
|
|
|
for (const file of DEV_ENV_FILES) {
|
|
const filePath = path.join(REPO_ROOT, file);
|
|
if (!fs.existsSync(filePath)) continue;
|
|
Object.assign(merged, parseDotenv(fs.readFileSync(filePath)));
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
/**
|
|
* Removes the values bun copied out of a plain `.env` on start-up.
|
|
*
|
|
* bun reads `.env` into process.env before the first line of a script runs, and
|
|
* nothing downstream can tell that apart from `DATABASE_URL=… bun run test:api`.
|
|
* Under the "an already-exported variable wins" contract the development `.env`
|
|
* therefore beat `.env.test` outright, which pointed the api suites at whatever
|
|
* deployment `.env` describes: `prisma db push --accept-data-loss` for the
|
|
* schema, then a truncate of every table between tests. The same values reached
|
|
* the app the e2e suite builds, R2 credentials included.
|
|
*
|
|
* Only entries whose current value is character-for-character what `.env` holds
|
|
* are dropped, so a real export still wins, which is what the per-suite
|
|
* databases of a parallel api run rely on. CI has no `.env`, so this is a no-op
|
|
* there.
|
|
*/
|
|
export function forgetAutoloadedDotenv(): void {
|
|
for (const [key, value] of Object.entries(readDevEnv())) {
|
|
if (process.env[key] === value) {
|
|
delete process.env[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Every variable a development env file defines.
|
|
*
|
|
* Deleting them from process.env only gets you half way for the e2e suite:
|
|
* `next build` and `next start` run @next/env themselves, which reads the same
|
|
* files again and fills in whatever is undefined. playwright.config.ts uses this
|
|
* list to blank the ones it does not set, which is the state CI is already in.
|
|
*/
|
|
export function developmentEnvKeys(): string[] {
|
|
return Object.keys(readDevEnv());
|
|
}
|
|
|
|
/**
|
|
* Reads a single variable out of `.env.test` without loading the rest of it.
|
|
*
|
|
* For playwright.config.ts, which needs DATABASE_URL to agree with the suite
|
|
* that seeds the database but must keep the rest of that file away from a
|
|
* production `next build`.
|
|
*/
|
|
export function readTestEnvValue(key: string): string | undefined {
|
|
if (!fs.existsSync(TEST_ENV_PATH)) return undefined;
|
|
|
|
return parseDotenv(fs.readFileSync(TEST_ENV_PATH))[key];
|
|
}
|