import { $ } from 'bun'; import { createServer } from 'net'; import { rmSync, mkdirSync, writeFileSync } from 'fs'; import path from 'path'; import type { Page } from 'playwright'; export const DATA_DIR = './data-test'; export const ADMIN_PASS = 'testpass123'; /** Bind to port 0 and return the OS-assigned free port number. */ function getFreePort(): Promise { return new Promise((resolve, reject) => { const srv = createServer(); srv.listen(0, '127.0.0.1', () => { const port = (srv.address() as { port: number }).port; srv.close((err) => (err ? reject(err) : resolve(port))); }); srv.on('error', reject); }); } // Resolved once in setupTestEnv() and re-exported for tests. export let PORT = 0; export let BASE = ''; export async function setupTestEnv() { PORT = await getFreePort(); BASE = `http://localhost:${PORT}`; rmSync(DATA_DIR, { recursive: true, force: true }); mkdirSync(`${DATA_DIR}/repos`, { recursive: true }); const init = Bun.spawn(['bun', 'run', 'src/db/init.ts'], { env: { ...process.env, DATA_DIR, ADMIN_PASSWORD: ADMIN_PASS }, stdout: 'pipe', stderr: 'pipe', }); await init.exited; } export function spawnServer() { return Bun.spawn(['bun', 'run', 'src/index.tsx'], { env: { ...process.env, PORT: String(PORT), DATA_DIR, RATE_LIMIT_DISABLED: 'true', SSH_DISABLED: 'true', }, stdout: 'ignore', stderr: 'ignore', }); } export async function waitForServer(maxMs = 10_000) { const deadline = Date.now() + maxMs; while (Date.now() < deadline) { try { await fetch(BASE); return; } catch { await Bun.sleep(150); } } throw new Error('Server did not start in time'); } export async function login( page: Page, username = 'admin', password = ADMIN_PASS, ) { await page.goto(`${BASE}/login`); await page.fill('[name=username]', username); await page.fill('[name=password]', password); await page.click('button[type=submit]'); await page.waitForURL(BASE + '/'); } export async function logout(page: Page) { await page.click('form[action="/logout"] button'); await page.waitForURL(BASE + '/'); } /** Push an initial commit into a bare repo that already exists on disk. */ export async function seedRepo(name: string) { const repoPath = `${process.cwd()}/${DATA_DIR}/repos/${name}.git`; const tmp = `/tmp/hf-seed-${Date.now()}`; try { await $`git clone ${repoPath} ${tmp}`.quiet(); await $`git -C ${tmp} config user.email "test@test.com"`.quiet(); await $`git -C ${tmp} config user.name "Test"`.quiet(); writeFileSync(`${tmp}/README.md`, `# ${name}\n`); writeFileSync(`${tmp}/index.js`, `console.log("hello");\n`); await $`git -C ${tmp} add -A`.quiet(); await $`git -C ${tmp} commit -m "Initial commit"`.quiet(); await $`git -C ${tmp} push origin HEAD:main`.quiet(); } finally { await $`rm -rf ${tmp}`.quiet().nothrow(); } } export function writeTempFile(path: string, content: string) { writeFileSync(path, content); } /** Commit a subdirectory with the given files into an existing repo on main. */ export async function seedSubdir(repoName: string, dirPath: string, files: Record) { const repoDir = `${process.cwd()}/${DATA_DIR}/repos/${repoName}.git`; const tmp = `/tmp/hf-subdir-${Date.now()}`; try { await $`git clone ${repoDir} ${tmp}`.quiet(); await $`git -C ${tmp} config user.email "test@test.com"`.quiet(); await $`git -C ${tmp} config user.name "Test"`.quiet(); mkdirSync(path.join(tmp, dirPath), { recursive: true }); for (const [fileName, content] of Object.entries(files)) { writeFileSync(path.join(tmp, dirPath, fileName), content); } await $`git -C ${tmp} add -A`.quiet(); await $`git -C ${tmp} commit -m ${'Add ' + dirPath}`.quiet(); await $`git -C ${tmp} push origin HEAD:main`.quiet(); } finally { await $`rm -rf ${tmp}`.quiet().nothrow(); } } /** Return the HEAD commit hash of a repo. */ export async function getHeadCommit(repoName: string): Promise { const repoPath = `${process.cwd()}/${DATA_DIR}/repos/${repoName}.git`; return (await $`git -C ${repoPath} rev-parse HEAD`.quiet()).text().trim(); } /** Create a new branch in an existing repo (from current HEAD). */ export async function seedBranch(name: string, branchName: string) { const repoPath = `${process.cwd()}/${DATA_DIR}/repos/${name}.git`; const tmp = `/tmp/hf-branch-${Date.now()}`; try { await $`git clone ${repoPath} ${tmp}`.quiet(); await $`git -C ${tmp} config user.email "test@test.com"`.quiet(); await $`git -C ${tmp} config user.name "Test"`.quiet(); await $`git -C ${tmp} checkout -b ${branchName}`.quiet(); await $`git -C ${tmp} push origin ${branchName}`.quiet(); } finally { await $`rm -rf ${tmp}`.quiet().nothrow(); } }