import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync, utimesSync, symlinkSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { EventEmitter } from 'node:events'; import { PassThrough } from 'node:stream'; import { createJobs, createGit } from '../src/jobs.js'; const TOKEN = 'tok_SECRET_123456'; const B64 = Buffer.from(`x-access-token:${TOKEN}`).toString('base64'); function setup({ files, dockerImpl, mem = () => 10 * 1024 ** 3, cfg = {}, git } = {}) { const root = mkdtempSync(join(tmpdir(), 'jobs-')); const config = { giteaUrl: 'https://git.x', giteaToken: TOKEN, previewsRoot: join(root, 'previews'), workRoot: join(root, 'work'), cacheRoot: join(root, 'cache'), minFreeBytes: 1000, buildTimeoutMs: 900000, ...cfg }; mkdirSync(config.previewsRoot, { recursive: true }); const calls = []; const chowned = []; const timers = []; const docker = { run: async (args, opts) => { calls.push({ args, opts }); return dockerImpl ? dockerImpl(args, opts) : { code: 0 }; } }; const gitFake = git || (async ({ dir }) => { for (const [f, c] of Object.entries(files || {})) { mkdirSync(join(dir, f, '..'), { recursive: true }); writeFileSync(join(dir, f), c); } mkdirSync(join(dir, '.git'), { recursive: true }); writeFileSync(join(dir, '.git', 'config'), 'x'); }); const jobs = createJobs({ config, docker, git: gitFake, mem, fs: { chownR: async (p) => { chowned.push(p); } }, setTimer: (fn, ms) => timers.push({ fn, ms }) }); return { root, config, calls, chowned, timers, jobs }; } const job = (o = {}) => ({ sub: 'demo', repo: 'own/rep', sha: 'abcdef1', branch: 'main', mode: 'auto', env: {}, ...o }); const wait = async (jobs, id) => { for (let i = 0; i < 400; i++) { const r = jobs.get(id); if (r.state === 'done' || r.state === 'failed') return r; await new Promise((r2) => setTimeout(r2, 5)); } throw new Error('timeout'); }; const workOf = (args) => args.find((a) => a.endsWith(':/work')).split(':')[0]; const PKG = (extra = {}) => JSON.stringify({ scripts: { build: 'vite build', ...extra } }); test('static: kopiert ohne .git, Rechte 755/644', async () => { const s = setup({ files: { 'index.html': 'hi', 'a/b.css': 'x' } }); const r = await wait(s.jobs, s.jobs.submit(job())); assert.equal(r.state, 'done'); assert.equal(r.result, 'static'); const t = join(s.config.previewsRoot, 'demo'); assert.equal(readFileSync(join(t, 'index.html'), 'utf8'), 'hi'); assert.ok(!existsSync(join(t, '.git'))); assert.equal(statSync(join(t, 'a')).mode & 0o777, 0o755); assert.equal(statSync(join(t, 'a/b.css')).mode & 0o777, 0o644); assert.ok(!existsSync(join(s.config.workRoot, 'build', r.runId))); assert.equal(s.calls.length, 0); }); test('build: Analyse wird in den gebauten Ausgabeordner eingesetzt (wie bei static), Dev nicht', async () => { const analytics = { siteKey: 'abcdefghij0123456789', color: '#2b6cb0', privacy: { company: 'Muster GmbH', address: 'Weg 1, 12345 Ort', email: 'info@muster.de' } }; const s = setup({ cfg: { analyticsPublicBase: 'https://project.webklar.com/a/' }, files: { 'package.json': PKG() }, dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'index.html'), 'NEU'); } return { code: 0 }; }, }); const r = await wait(s.jobs, s.jobs.submit(job({ mode: 'build', analytics }))); assert.equal(r.state, 'done'); assert.equal(r.result, 'build'); const t = join(s.config.previewsRoot, 'demo'); assert.match(readFileSync(join(t, 'index.html'), 'utf8'), /