test(api): Infra (health/db/schema/seed/env/migrate) — fill gaps + lifecycle test

Drives all infra src files to 100% (env.ts, db/client.ts, db/ensure-dir.ts,
db/migrate.ts, db/seed.ts, routes/health.ts).

- env.test.ts: dev defaults when unset, overrides when set (PORT coerced),
  CORS_ORIGINS parsing (trim + drop empties) and empty-fallback.
- db.test.ts: runMigrations idempotency; ensureDbDir no-op for non-file URLs
  and bare filenames (branch coverage).
- seed.test.ts: production guard — no dev users seeded when NODE_ENV=production.
- c8-ignore the migrate.ts/seed.ts CLI direct-run blocks (unreachable under vitest).

No lifecycle test (infra domain, per plan). No bugs found — infra code correct;
only coverage gaps. All 112 API tests green; typecheck + oxlint clean.
This commit is contained in:
Bas van Rossem
2026-07-22 11:39:34 +02:00
parent 0564fab1e6
commit 2fa8aa9154
6 changed files with 133 additions and 1 deletions

60
apps/api/test/env.test.ts Normal file
View File

@@ -0,0 +1,60 @@
import { describe, it, expect, afterEach, vi } from 'vitest';
// env.ts reads process.env once at module-eval time, so each case re-imports it
// under a controlled environment (resetModules busts the cached module), then
// restores the original process.env immediately so nothing leaks to other tests.
const KEYS = ['DATABASE_URL', 'BETTER_AUTH_SECRET', 'BETTER_AUTH_URL', 'PORT', 'CORS_ORIGINS'];
async function loadEnv(overrides: Record<string, string | undefined>) {
const saved: Record<string, string | undefined> = {};
for (const k of KEYS) saved[k] = process.env[k];
for (const k of KEYS) delete process.env[k];
for (const [k, v] of Object.entries(overrides)) if (v !== undefined) process.env[k] = v;
vi.resetModules();
try {
const { env } = await import('../src/env');
return env;
} finally {
for (const k of KEYS) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
}
}
describe('env', () => {
afterEach(() => vi.resetModules());
it('applies every dev default when the vars are unset', async () => {
const env = await loadEnv({});
expect(env.DATABASE_URL).toBe('file:./data/app.db');
expect(env.BETTER_AUTH_SECRET).toBe('dev-insecure-secret-change-me');
expect(env.BETTER_AUTH_URL).toBe('http://localhost:3000');
expect(env.PORT).toBe(3000);
expect(env.WEB_ORIGINS).toEqual(['http://localhost:5173', 'http://localhost:5174']);
});
it('reads each override when the var is set (PORT coerced to a number)', async () => {
const env = await loadEnv({
DATABASE_URL: 'file:./other.db',
BETTER_AUTH_SECRET: 'top-secret',
BETTER_AUTH_URL: 'https://api.example.com',
PORT: '8080',
});
expect(env.DATABASE_URL).toBe('file:./other.db');
expect(env.BETTER_AUTH_SECRET).toBe('top-secret');
expect(env.BETTER_AUTH_URL).toBe('https://api.example.com');
expect(env.PORT).toBe(8080);
expect(typeof env.PORT).toBe('number');
});
it('parses CORS_ORIGINS into WEB_ORIGINS, trimming and dropping empty entries', async () => {
const env = await loadEnv({ CORS_ORIGINS: ' http://a.example , http://b.example , ' });
expect(env.WEB_ORIGINS).toEqual(['http://a.example', 'http://b.example']);
});
it('falls back to the default origins when CORS_ORIGINS is empty', async () => {
const env = await loadEnv({ CORS_ORIGINS: '' });
expect(env.WEB_ORIGINS).toEqual(['http://localhost:5173', 'http://localhost:5174']);
});
});