import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import test from 'node:test'; import { SAMPLE_GPX } from '../test-support/gpx-fixtures.js'; const script = path.resolve(import.meta.dirname, '../scripts/upload-presentation-route.sh'); async function uploadFixture(t) { const runtime = await fs.mkdtemp(path.join(os.tmpdir(), 'wegwichtel-presentation-')); t.after(() => fs.rm(runtime, { recursive: true, force: true })); const bin = path.join(runtime, 'bin'); await fs.mkdir(bin); const gpxPath = path.join(runtime, 'track.gpx'); const jsonPath = path.join(runtime, 'stations.json'); const logPath = path.join(runtime, 'requests.jsonl'); const metadata = { name: 'Testweg', description: 'Synthetic upload data', pois: [ { title: 'Start', description: 'First station', lat: 52.5200, lon: 13.4050, triggerRadiusM: 25, sequence: 0 }, { title: 'Ende', description: 'Last station', lat: 52.5216, lon: 13.4090, triggerRadiusM: 30, sequence: 1 } ] }; await fs.writeFile(gpxPath, SAMPLE_GPX); await fs.writeFile(jsonPath, JSON.stringify(metadata)); // Capture curl arguments locally. No requests are sent to a real server. await fs.writeFile(path.join(bin, 'curl'), `#!/usr/bin/env python3 import json, os, sys from pathlib import Path log = Path(os.environ['TEST_REQUEST_LOG']) count = len(log.read_text().splitlines()) if log.exists() else 0 with log.open('a') as output: output.write(json.dumps(sys.argv[1:]) + '\\n') if os.environ.get('TEST_FAIL_POI') == '1' and count == 1: sys.exit(22) print(json.dumps({'id': 101})) `, { mode: 0o755 }); return { gpxPath, jsonPath, logPath, metadata, env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`, TEST_REQUEST_LOG: logPath } }; } test('presentation uploader accepts explicit test files and posts route and stations through the API', async t => { const fixture = await uploadFixture(t); const result = spawnSync('bash', [script, 'https://example.invalid/api', fixture.gpxPath, fixture.jsonPath], { env: fixture.env, encoding: 'utf8', timeout: 10000 }); assert.equal(result.status, 0, result.stderr); const requests = (await fs.readFile(fixture.logPath, 'utf8')).trim().split('\n').map(line => JSON.parse(line)); assert.equal(requests.length, 3); assert.ok(requests[0].includes('https://example.invalid/api/routes')); assert.ok(requests[0].includes(`gpx=@${fixture.gpxPath};type=application/gpx+xml`)); for (const [index, poi] of fixture.metadata.pois.entries()) { assert.ok(requests[index + 1].includes('https://example.invalid/api/routes/101/pois')); assert.ok(requests[index + 1].includes(`title=${poi.title}`)); assert.ok(requests[index + 1].includes(`sequence=${poi.sequence}`)); assert.ok(requests[index + 1].includes('POST')); } assert.match(result.stdout, /Server-ID: 101/); }); test('presentation uploader reports missing files before any HTTP request', async t => { const fixture = await uploadFixture(t); for (const args of [[], ['https://example.invalid/api', fixture.gpxPath, `${fixture.jsonPath}.missing`]]) { const result = spawnSync('bash', [script, ...args], { env: fixture.env, encoding: 'utf8', timeout: 10000 }); assert.equal(result.status, 2); assert.match(result.stderr, /Aufruf:/); } await assert.rejects(fs.access(fixture.logPath), { code: 'ENOENT' }); }); test('presentation uploader removes an incomplete route if a station upload fails', async t => { const fixture = await uploadFixture(t); const result = spawnSync('bash', [script, 'https://example.invalid/api', fixture.gpxPath, fixture.jsonPath], { env: { ...fixture.env, TEST_FAIL_POI: '1' }, encoding: 'utf8', timeout: 10000 }); assert.notEqual(result.status, 0); const requests = (await fs.readFile(fixture.logPath, 'utf8')).trim().split('\n').map(line => JSON.parse(line)); assert.equal(requests.length, 3); assert.ok(requests[2].includes('DELETE')); assert.ok(requests[2].includes('https://example.invalid/api/routes/101')); });