diff --git a/analytics/scripts/geo-update.sh b/analytics/scripts/geo-update.sh index a0db4a5..a4fe449 100755 --- a/analytics/scripts/geo-update.sh +++ b/analytics/scripts/geo-update.sh @@ -8,6 +8,7 @@ DIR="${ANALYTICS_DATA_DIR:-/srv/webklar-analytics}" CONTAINER="${ANALYTICS_CONTAINER:-webklar-analytics}" NEU="$DIR/geo.csv.new" ZIEL="$DIR/geo.csv" +MARKER="$DIR/geo.restart-pending" fehler() { echo "Fehler (geo-update): $*" >&2; rm -f "$NEU" "$NEU.gz"; exit 1; } @@ -36,13 +37,24 @@ zeilen="$(wc -l < "$NEU" | tr -d ' ')" [ "$zeilen" -gt 100000 ] || fehler "Neue Datei hat nur $zeilen Zeilen (erwartet > 100000). Bestehende geo.csv bleibt unverändert." grep -Eq ',"?DE"?\r?$' "$NEU" || fehler "Neue Datei enthält kein 'DE'. Bestehende geo.csv bleibt unverändert." +neustart() { + docker restart "$CONTAINER" >/dev/null || fehler "Neustart von $CONTAINER fehlgeschlagen (geo.csv ist aber aktualisiert, nächster Lauf versucht den Neustart erneut)." + rm -f "$MARKER" + echo "$CONTAINER neu gestartet." +} + if [ -f "$ZIEL" ] && cmp -s "$NEU" "$ZIEL"; then rm -f "$NEU" - echo "geo.csv ($geladen) unverändert, kein Neustart nötig." + if [ -f "$MARKER" ]; then + echo "geo.csv ($geladen) unverändert, aber ein früherer Neustart steht aus." + neustart + else + echo "geo.csv ($geladen) unverändert, kein Neustart nötig." + fi exit 0 fi mv -f "$NEU" "$ZIEL" +: > "$MARKER" # bleibt liegen, bis der Neustart gelungen ist echo "geo.csv aktualisiert ($geladen, $zeilen Zeilen)." -docker restart "$CONTAINER" >/dev/null || fehler "Neustart von $CONTAINER fehlgeschlagen (geo.csv ist aber aktualisiert)." -echo "$CONTAINER neu gestartet." +neustart diff --git a/analytics/test/geo-update.test.js b/analytics/test/geo-update.test.js new file mode 100644 index 0000000..f0a1368 --- /dev/null +++ b/analytics/test/geo-update.test.js @@ -0,0 +1,52 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtempSync, mkdirSync, writeFileSync, existsSync, readFileSync, chmodSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const SCRIPT = new URL('../scripts/geo-update.sh', import.meta.url).pathname; + +// Fremde Programme (curl, docker) sind Attrappen im PATH; docker schlaegt fehl, solange DOCKER_FAIL=1 gesetzt ist. +function setup() { + const root = mkdtempSync(join(tmpdir(), 'geoupd-')); + const bin = join(root, 'bin'); + const data = join(root, 'data'); + mkdirSync(bin); + mkdirSync(data); + const csv = join(root, 'src.csv'); + const rows = []; + for (let i = 0; i < 100100; i++) rows.push(`1.${i % 250}.${Math.floor(i / 250) % 250}.0,1.${i % 250}.${Math.floor(i / 250) % 250}.255,AU`); + rows.push('5.9.0.0,5.9.255.255,DE'); + writeFileSync(csv, rows.join('\n') + '\n'); + writeFileSync(join(bin, 'curl'), `#!/bin/sh\nwhile [ "$#" -gt 0 ]; do [ "$1" = "-o" ] && out="$2"; shift; done\ngzip -c "${csv}" > "$out"\n`); + writeFileSync(join(bin, 'docker'), `#!/bin/sh\necho "$@" >> "${root}/docker.log"\n[ "\${DOCKER_FAIL:-0}" = "1" ] && exit 1\nexit 0\n`); + chmodSync(join(bin, 'curl'), 0o755); + chmodSync(join(bin, 'docker'), 0o755); + const run = (fail) => spawnSync('sh', [SCRIPT], { env: { PATH: `${bin}:${process.env.PATH}`, ANALYTICS_DATA_DIR: data, DOCKER_FAIL: fail ? '1' : '0' }, encoding: 'utf8' }); + const restarts = () => (existsSync(join(root, 'docker.log')) ? readFileSync(join(root, 'docker.log'), 'utf8').trim().split('\n').length : 0); + return { data, run, restarts, marker: join(data, 'geo.restart-pending') }; +} + +test('geo-update: Neustart schlägt fehl -> Marker; nächster Lauf startet trotz unveränderter Datei neu und entfernt ihn', () => { + const s = setup(); + const first = s.run(true); + assert.notEqual(first.status, 0, first.stderr); + assert.ok(existsSync(join(s.data, 'geo.csv')), 'neue Datei ist schon an Ort und Stelle'); + assert.ok(existsSync(s.marker), 'Marker liegt'); + assert.equal(s.restarts(), 1); + const second = s.run(false); + assert.equal(second.status, 0, second.stderr); + assert.equal(s.restarts(), 2, 'Neustart nachgeholt'); + assert.equal(existsSync(s.marker), false); + const third = s.run(false); + assert.equal(third.status, 0); + assert.equal(s.restarts(), 2, 'unverändert und ohne Marker: kein Neustart'); +}); + +test('geo-update: erfolgreicher Neustart hinterlässt keinen Marker', () => { + const s = setup(); + assert.equal(s.run(false).status, 0); + assert.equal(s.restarts(), 1); + assert.equal(existsSync(s.marker), false); +}); diff --git a/runner/test/jobs.test.js b/runner/test/jobs.test.js index 7f08037..fe22bc9 100644 --- a/runner/test/jobs.test.js +++ b/runner/test/jobs.test.js @@ -40,6 +40,24 @@ test('static: kopiert ohne .git, Rechte 755/644', async () => { 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'), /