Files
Webklar-Kundenbereich/runner/test/index-fallback.test.js
T

169 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createJobs } from '../src/jobs.js';
import { validateJob } from '../src/validate.js';
function setup({ files, dockerImpl }) {
const root = mkdtempSync(join(tmpdir(), 'idx-'));
const config = { giteaUrl: 'https://git.x', giteaToken: 'tok_SECRET_123456', previewsRoot: join(root, 'previews'), workRoot: join(root, 'work'), cacheRoot: join(root, 'cache'), minFreeBytes: 1000, buildTimeoutMs: 900000 };
const 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 docker = { run: async (args) => (dockerImpl ? dockerImpl(args) : { code: 0 }) };
const jobs = createJobs({ config, docker, git, mem: () => 1e10, fs: { chownR: async () => {} }, setTimer: () => {} });
return { config, jobs, out: join(config.previewsRoot, 'demo') };
}
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];
test('Doener-Fall: nur Doener_Nation_Website.html ⇒ index.html ist Byte-Kopie, Original bleibt', async () => {
const html = Buffer.from('<!doctype html><title>Döner</title>äö\n');
const s = setup({ files: { 'Doener_Nation_Website.html': html, '.webklar-preview.json': '{}' } });
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'done');
assert.deepEqual(readFileSync(join(s.out, 'index.html')), html);
assert.deepEqual(readFileSync(join(s.out, 'Doener_Nation_Website.html')), html);
assert.equal(statSync(join(s.out, 'index.html')).mode & 0o777, 0o644);
});
test('vorhandene index.html bleibt unberührt', async () => {
const s = setup({ files: { 'index.html': 'ORIG', 'home.html': 'HOME' } });
await wait(s.jobs, s.jobs.submit(job()));
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'ORIG');
});
test('bevorzugter Name gewinnt, sonst größte Datei', async () => {
let s = setup({ files: { 'a.html': 'aaaa', 'Main.html': 'm', 'zz.html': 'zzzzzzzz' } });
await wait(s.jobs, s.jobs.submit(job()));
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'm');
s = setup({ files: { 'a.html': 'aaaa', 'b.htm': 'bbbbbbbbb', 'c.html': 'c' } });
await wait(s.jobs, s.jobs.submit(job()));
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'bbbbbbbbb');
});
test('index-Unterordner wird Webroot; fehlender Ordner ⇒ Repo-Wurzel', async () => {
let s = setup({ files: { 'public/index.html': 'PUB', 'x.html': 'root' } });
let r = await wait(s.jobs, s.jobs.submit(job({ index: 'public' })));
assert.equal(r.state, 'done');
assert.deepEqual(readdirSync(s.out), ['index.html']);
s = setup({ files: { 'x.html': 'root' } });
await wait(s.jobs, s.jobs.submit(job({ index: 'gibtsnicht' })));
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'root');
});
test('kein HTML ⇒ Platzhalter mit Anzeigename (maskiert)', async () => {
const s = setup({ files: { 'readme.txt': 'x' } });
await wait(s.jobs, s.jobs.submit(job({ displayName: 'A & <B>' })));
const h = readFileSync(join(s.out, 'index.html'), 'utf8');
assert.match(h, /<h1>A &amp; &lt;B&gt;<\/h1>/);
assert.match(h, /<title>A &amp; &lt;B&gt; – wird vorbereitet<\/title>/);
const s2 = setup({ files: { 'readme.txt': 'x' } });
await wait(s2.jobs, s2.jobs.submit(job()));
assert.match(readFileSync(join(s2.out, 'index.html'), 'utf8'), /<h1>demo<\/h1>/);
});
test('Build ohne index.html im Ausgabeordner ⇒ Quellstand statisch + Kandidat/Platzhalter', async () => {
const s = setup({
files: { 'package.json': JSON.stringify({ scripts: { build: 'x' } }), 'seite.html': 'SEITE' },
dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'app.js'), 'x'); mkdirSync(join(workOf(args), 'node_modules'), { recursive: true }); writeFileSync(join(workOf(args), 'node_modules', 'm.js'), 'x'); } return { code: 0 }; },
});
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'done'); assert.equal(r.result, 'static');
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'SEITE');
assert.ok(!existsSync(join(s.out, 'node_modules')));
const s2 = setup({
files: { 'package.json': JSON.stringify({ scripts: { build: 'x' } }) },
dockerImpl: (args) => { if (args.includes('build')) mkdirSync(join(workOf(args), 'dist'), { recursive: true }); return { code: 0 }; },
});
await wait(s2.jobs, s2.jobs.submit(job({ displayName: 'Shop' })));
assert.match(readFileSync(join(s2.out, 'index.html'), 'utf8'), /<h1>Shop<\/h1>/);
});
test('Build-Ausgabe mit index.html wird unverändert veröffentlicht', async () => {
const s = setup({
files: { 'package.json': JSON.stringify({ scripts: { build: 'x' } }) },
dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'index.html'), 'B'); writeFileSync(join(workOf(args), 'dist', 'o.html'), 'O'); } return { code: 0 }; },
});
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.result, 'build');
assert.deepEqual(readdirSync(s.out).sort(), ['index.html', 'o.html']);
});
const BUILD_PKG = JSON.stringify({ scripts: { build: 'x' } });
const failBuild = (args) => ({ code: args.includes('build') ? 1 : 0 });
test('bestehender <sub>-Ordner + fehlgeschlagener Build ⇒ failed, alter Stand unverändert', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG, 'x.html': 'QUELLE' }, dockerImpl: failBuild });
mkdirSync(s.out, { recursive: true }); writeFileSync(join(s.out, 'index.html'), 'ALT');
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'failed');
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'ALT');
});
test('erstes Deploy + fehlgeschlagener Build ⇒ Quellstand statisch (Kandidat), result static, Hinweis', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG, 'x.html': 'QUELLE' }, dockerImpl: failBuild });
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'done'); assert.equal(r.result, 'static'); assert.match(r.hint, /Build fehlgeschlagen/);
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'QUELLE');
assert.ok(r.log.some((l) => /erstes Deploy/.test(l)));
});
test('erstes Deploy + Installationsfehler ⇒ statischer Quellstand', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG }, dockerImpl: () => ({ code: 1 }) });
const r = await wait(s.jobs, s.jobs.submit(job({ displayName: 'Shop' })));
assert.equal(r.result, 'static');
assert.match(readFileSync(join(s.out, 'index.html'), 'utf8'), /<h1>Shop<\/h1>/);
});
test('R21: json type static + index.html + build-Skript ⇒ static, kein Container', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG, 'index.html': 'S', '.webklar-preview.json': '{"type":"static"}' }, dockerImpl: () => { throw new Error('kein Container'); } });
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'done'); assert.equal(r.result, 'static');
});
test('R21: json type node_build ⇒ build (auch mit index.html); kein Dev-Rückfall', async () => {
const mk = (extra) => setup({ files: { 'package.json': JSON.stringify({ scripts: { build: 'x', dev: 'y' } }), 'index.html': 'S', '.webklar-preview.json': '{"type":"node_build"}', ...extra },
dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'index.html'), 'B'); } return { code: 0 }; } });
let s = mk();
let r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.result, 'build'); assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'B');
s = setup({ files: { 'package.json': JSON.stringify({ scripts: { build: 'x', dev: 'y' } }), '.webklar-preview.json': '{"type":"node_build"}' }, dockerImpl: failBuild });
mkdirSync(s.out, { recursive: true });
r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.state, 'failed');
});
test('R21: ohne json-Typ + index.html + build-Skript ⇒ build', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG, 'index.html': 'S' }, dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'index.html'), 'B'); } return { code: 0 }; } });
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.result, 'build');
});
test('R21: Modus build aus dem Portal schlägt json type static', async () => {
const s = setup({ files: { 'package.json': BUILD_PKG, 'index.html': 'S', '.webklar-preview.json': '{"type":"static"}' }, dockerImpl: (args) => { if (args.includes('build')) { mkdirSync(join(workOf(args), 'dist'), { recursive: true }); writeFileSync(join(workOf(args), 'dist', 'index.html'), 'B'); } return { code: 0 }; } });
const r = await wait(s.jobs, s.jobs.submit(job({ mode: 'build' })));
assert.equal(r.result, 'build');
});
test('json index-Unterordner wird beim Veröffentlichen genutzt (static und Build-Rückfall)', async () => {
let s = setup({ files: { 'public/index.html': 'PUB', 'root.html': 'r', '.webklar-preview.json': '{"index":"public"}' } });
await wait(s.jobs, s.jobs.submit(job()));
assert.deepEqual(readdirSync(s.out), ['index.html']);
s = setup({ files: { 'package.json': BUILD_PKG, 'public/home.html': 'H', '.webklar-preview.json': '{"index":"public"}' }, dockerImpl: failBuild });
const r = await wait(s.jobs, s.jobs.submit(job()));
assert.equal(r.result, 'static');
assert.equal(readFileSync(join(s.out, 'index.html'), 'utf8'), 'H');
assert.ok(!existsSync(join(s.out, 'package.json')));
});
test('validateJob: displayName optional, ≤100 Zeichen, keine Steuerzeichen', () => {
const good = { sub: 'a', repo: 'o/r', sha: 'abcdef1', branch: 'main' };
assert.equal(validateJob({ ...good, displayName: 'Döner Nation' }).job.displayName, 'Döner Nation');
assert.equal('displayName' in validateJob(good).job, false);
assert.equal(validateJob({ ...good, displayName: 'x'.repeat(101) }).ok, false);
assert.equal(validateJob({ ...good, displayName: 'a\nb' }).ok, false);
assert.equal(validateJob({ ...good, displayName: 5 }).ok, false);
});