import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { openDb, d1, migrate } from '../server/db.mjs'; import { hashPassword } from '../server/auth.mjs'; import { memoryRateStore } from '../server/util.mjs'; const HERE = dirname(fileURLToPath(import.meta.url)); export async function makeWwwDb({ login = 'arihakdang', password = 'admin-secret-12', email = 'admin@arihakdang.com', role = 'admin', status = 'active' } = {}) { const db = openDb(':memory:'); db.exec('CREATE TABLE users(id TEXT PRIMARY KEY,email TEXT UNIQUE,login_id TEXT,name TEXT,role TEXT,status TEXT,password_hash TEXT)'); db.prepare('INSERT INTO users VALUES(?,?,?,?,?,?,?)').run('admin-1', email, login, '관리자', role, status, await hashPassword(password, 1000)); db.prepare('INSERT INTO users VALUES(?,?,?,?,?,?,?)').run('student-1', 'student@example.test', 'student', '학생', 'student', 'active', await hashPassword(password, 1000)); return db; } export async function makeEnv(over = {}) { const db = openDb(':memory:'); migrate(db, join(HERE, '..', 'migrations'), { quiet: true }); return { DB: d1(db), RATE: memoryRateStore(), WWW_DB: await makeWwwDb(), MAILBOX: 'admin@arihakdang.com', RESEND_API_KEY: 'test', POP_AUTH_DELAY_MS: 0, ...over }; } export function runner() { let pass = 0, fail = 0; const t = async (name, fn) => { try { await fn(); console.log(' ok ' + name); pass++; } catch (e) { console.log(' FAIL ' + name + ' — ' + (e.stack || e.message)); fail++; } }; const done = () => { console.log(`\n${pass} passed, ${fail} failed`); process.exit(fail ? 1 : 0); }; return { t, done }; } export const eq = (a, b, m) => { if (a !== b) throw new Error(`${m}: ${JSON.stringify(a)} !== ${JSON.stringify(b)}`); }; export const ok = (v, m) => { if (!v) throw new Error(m); }; /** Resend 흉내: 수신 목록/상세/첨부/원본 다운로드/발송. */ export function fakeResend({ received = [], sent = [], raw = {}, attachments = {}, calls = [], failStatus = 0 } = {}) { return async (url, options = {}) => { const u = new URL(url), path = u.pathname; const method = options.method || 'GET'; calls.push({ path, method, body: options.body ? JSON.parse(options.body) : undefined, key: options.headers?.['Idempotency-Key'] }); if (failStatus) return Response.json({ message: 'API key is invalid' }, { status: failStatus }); if (path === '/domains') return Response.json({ data: [{ name: 'arihakdang.com', status: 'verified' }] }); if (path === '/emails' && method === 'POST') { const id = 'sent-' + calls.length; sent.push({ id, ...JSON.parse(options.body), created_at: new Date().toISOString() }); return Response.json({ id }); } if (path === '/emails/receiving') return Response.json({ data: received.map(({ html, text, headers, ...s }) => s), has_more: false }); if (path === '/emails') return Response.json({ data: sent, has_more: false }); let m; if ((m = path.match(/^\/emails\/receiving\/([^/]+)\/attachments$/))) return Response.json({ data: (attachments[m[1]] || []), has_more: false }); if ((m = path.match(/^\/emails\/receiving\/([^/]+)$/))) { const item = received.find((r) => r.id === m[1]); return item ? Response.json({ ...item, raw: raw[item.id] ? { download_url: 'https://files.test/raw/' + item.id, expires_at: '2099-01-01' } : null }) : Response.json({ message: 'not found' }, { status: 404 }); } if ((m = path.match(/^\/emails\/([^/]+)$/))) { const item = sent.find((r) => r.id === m[1]); return item ? Response.json(item) : Response.json({}, { status: 404 }); } if (u.hostname === 'files.test') { const id = path.split('/').pop(); if (path.startsWith('/raw/')) return new Response(raw[id]); const a = Object.values(attachments).flat().find((x) => x.id === id); return new Response(a?.bytes || 'file-bytes'); } return Response.json({ data: [], has_more: false }); }; } export const request = (path, cookie, method = 'GET', body, extraHeaders = {}) => new Request('https://mail.arihakdang.com/api/' + path, { method, headers: { ...(cookie ? { Cookie: cookie } : {}), ...(body instanceof FormData ? {} : { 'Content-Type': 'application/json' }), ...extraHeaders }, body: body instanceof FormData ? body : body === undefined ? undefined : typeof body === 'string' ? body : JSON.stringify(body) }); export const params = (path, ip = '127.0.0.1') => ({ path: path.split('?')[0].split('/').filter(Boolean), ip });