Files
nanodrop/tests/helpers/setup.ts
Brendan Chen a4355e1ef3 refactor(auth): drop JWT_EXPIRY env var (family TTL is canonical)
The JWT lifetime is now pinned to SESSION_TTL_SECONDS (30 days)
inside issueSessionCookie, so JWT_EXPIRY had no effect after the
prior commits. Removing the field from Config and the env read in
loadConfig lets the TypeScript compiler verify no caller was still
relying on it.

Per the family invariant ('coherence across apps is a feature'),
the per-app override is intentionally gone — a deploy with stale
JWT_EXPIRY in .env will now silently use the 30-day family default
regardless of the value.

Test fixtures (Config literals in setup.ts, login-handler.test.ts,
lockout-service.test.ts) and config.test.ts assertions updated to
match the new shape.
2026-05-09 10:17:04 -07:00

96 lines
2.7 KiB
TypeScript

import { mkdtempSync, rmSync, mkdirSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { initDb } from '../../src/db/schema.ts';
import { createServer } from '../../src/server.ts';
import { SESSION_COOKIE_NAME } from '../../src/constants.ts';
import type { Config } from '../../src/config.ts';
import type Database from 'better-sqlite3';
import type { FastifyInstance } from 'fastify';
export async function loginAs(app: FastifyInstance, username: string, password: string): Promise<string> {
const res = await app.inject({
method: 'POST',
url: '/api/v1/auth/login',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const cookie = res.headers['set-cookie'] as string;
return cookie.split(';')[0].replace(`${SESSION_COOKIE_NAME}=`, '');
}
interface MultipartFile {
filename: string;
contentType: string;
data: Buffer;
}
export function buildMultipart(files: Record<string, MultipartFile>): { payload: Buffer; headers: Record<string, string> } {
const boundary = '----TestBoundary' + Math.random().toString(36).slice(2);
const parts: Buffer[] = [];
for (const [fieldname, file] of Object.entries(files)) {
parts.push(Buffer.from(
`--${boundary}\r\n` +
`Content-Disposition: form-data; name="${fieldname}"; filename="${file.filename}"\r\n` +
`Content-Type: ${file.contentType}\r\n\r\n`,
));
parts.push(file.data);
parts.push(Buffer.from('\r\n'));
}
parts.push(Buffer.from(`--${boundary}--\r\n`));
return {
payload: Buffer.concat(parts),
headers: { 'content-type': `multipart/form-data; boundary=${boundary}` },
};
}
export interface TestContext {
app: FastifyInstance;
db: Database.Database;
uploadDir: string;
logFile: string;
cleanup: () => void;
}
export function createTestApp(overrides: Partial<Config> = {}): TestContext {
const tmpDir = mkdtempSync(join(tmpdir(), 'nanodrop-int-'));
const uploadDir = join(tmpDir, 'uploads');
const logFile = join(tmpDir, 'test.log');
mkdirSync(uploadDir, { recursive: true });
const db = initDb(':memory:');
const config: Config = {
port: 0,
host: '127.0.0.1',
jwtSecret: 'test-secret-key',
dbPath: ':memory:',
uploadDir,
logFile,
maxFileSize: 10 * 1024 * 1024,
baseUrl: 'http://localhost:3000',
cookieSecure: false,
trustProxy: false,
lockoutThreshold: 5,
lockoutBaseSeconds: 30,
lockoutMaxSeconds: 3600,
loginMinResponseMs: 0,
loginRateLimitMax: 1000,
loginRateLimitWindowSeconds: 60,
...overrides,
};
const app = createServer({ config, db });
return {
app,
db,
uploadDir,
logFile,
cleanup: () => rmSync(tmpDir, { recursive: true, force: true }),
};
}