import { randomBytes } from 'node:crypto'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { afterEach } from 'vitest'; import { buildApp } from '../src/app.js'; import type { Config } from '../src/config.js'; export const SUPERADMIN = 'root@example.com'; const tempDirs: string[] = []; afterEach(async () => { await Promise.all(tempDirs.splice(0).map((d) => rm(d, { recursive: true, force: true }))); }); export async function tempDir(): Promise { const dir = await mkdtemp(path.join(tmpdir(), 'cardy-test-')); tempDirs.push(dir); return dir; } export async function testConfig(overrides: Partial = {}): Promise { return { dataDir: await tempDir(), keySecret: randomBytes(32), superadminEmail: SUPERADMIN, superadminGroup: 'default', publicApiUrl: 'https://cards.example.com/api', host: '127.0.0.1', port: 0, ...overrides, }; } export async function testApp(overrides: Partial = {}) { const config = await testConfig(overrides); const app = await buildApp(config); return { app, config }; } export function multipart(parts: Array<{ name: string; value: string } | { name: string; filename: string; mime: string; data: Buffer }>) { const boundary = `----cardy${randomBytes(8).toString('hex')}`; const chunks: Buffer[] = []; for (const part of parts) { let head = `--${boundary}\r\nContent-Disposition: form-data; name="${part.name}"`; if ('filename' in part) head += `; filename="${part.filename}"\r\nContent-Type: ${part.mime}`; chunks.push(Buffer.from(`${head}\r\n\r\n`), 'data' in part ? part.data : Buffer.from(part.value), Buffer.from('\r\n')); } chunks.push(Buffer.from(`--${boundary}--\r\n`)); return { payload: Buffer.concat(chunks), headers: { 'content-type': `multipart/form-data; boundary=${boundary}` } }; }