directory cleanup for tests
Sonarqube Scanner / Prepare environment (push) Successful in 11s
Sonarqube Scanner / Build and test (push) Successful in 29s
Sonarqube Scanner / SonarQube analysis (push) Successful in 1m25s
Sonarqube Scanner / Deploy to Server (push) Successful in 7s

This commit is contained in:
Florian Zumpe
2026-10-04 08:12:08 +02:00
parent 4e7b519275
commit 497b7b8050
6 changed files with 120 additions and 48 deletions
+8 -28
View File
@@ -1,33 +1,13 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { parseGpx } from '../src/services/gpx.js';
import { SAMPLE_GPX } from '../test-support/gpx-fixtures.js';
test('parseGpx liest Beispielstrecke', () => {
const parsed = parseGpx(fs.readFileSync(new URL('../examples/sample-route.gpx', import.meta.url), 'utf8'));
assert.equal(parsed.name, 'Beispielweg'); assert.equal(parsed.points.length, 3); assert.ok(parsed.metrics.distanceM > 0);
});
test('normalization separates waypoints and collapses stationary GPS drift', () => {
const xml = '<gpx><metadata><name>Pause</name></metadata><wpt lat="51" lon="11"><name>Station</name></wpt><trk><trkseg>'
+ '<trkpt lat="50" lon="10"><time>2026-10-03T10:00:00Z</time></trkpt>'
+ '<trkpt lat="50.00001" lon="10.00001"/><trkpt lat="50.00002" lon="10.00002"/>'
+ '<trkpt lat="50.001" lon="10.001"/></trkseg></trk></gpx>';
const result = parseGpx(xml);
assert.equal(result.points.length, 2);
assert.equal(result.points[0].recordedAt, '2026-10-03T10:00:00Z');
assert.match(result.normalizedXml, /<wpt /);
assert.match(result.normalizedXml, /<rtept /);
assert.doesNotMatch(result.normalizedXml, /<trkpt /);
assert.deepEqual(parseGpx(result.normalizedXml).points, result.points);
});
test('waypoint-only routes and missing-position recordings have no invented route coordinates', () => {
const waypoint = parseGpx('<gpx><wpt lat="50" lon="10"/></gpx>');
assert.equal(waypoint.points.length, 0); assert.equal(waypoint.metrics.distanceM, 0);
const empty = parseGpx('<gpx xmlns:wegwichtel="urn:wegwichtel:poi"><extensions><wegwichtel:gpsGap time="2026-10-03T10:00:00Z"/></extensions></gpx>');
assert.equal(empty.points.length, 0); assert.equal(empty.metrics.startLat, null);
assert.match(empty.normalizedXml, /gpsGap/);
assert.throws(() => parseGpx('<gpx><trk>'));
assert.throws(() => parseGpx('<gpx><trk><trkseg><trkpt lat="100" lon="10"/></trkseg></trk></gpx>'));
test('parseGpx reads synthetic track data without repository example files', () => {
const parsed = parseGpx(SAMPLE_GPX);
assert.equal(parsed.name, 'Testweg');
assert.equal(parsed.points.length, 3);
assert.ok(parsed.metrics.distanceM > 0);
assert.equal(parsed.points[0].lat, 52.5200);
assert.equal(parsed.points[2].lon, 13.4090);
});
+4 -2
View File
@@ -1,4 +1,5 @@
import { parseGpx } from '../src/services/gpx.js';
import { SAMPLE_GPX } from '../test-support/gpx-fixtures.js';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
@@ -182,8 +183,9 @@ assert result['ok'] is True
[pythonHealthCheck.stdout, pythonHealthCheck.stderr].filter(Boolean).join('\n')
);
const gpxPath = path.join(root, 'examples', 'sample-route.gpx');
const gpx = await fs.readFile(gpxPath);
const gpxPath = path.join(runtime, 'test-route.gpx');
const gpx = Buffer.from(SAMPLE_GPX);
await fs.writeFile(gpxPath, gpx);
const createRoute = await runProcess('python3', [
path.join(root, 'tools/python/create_route.py'),
'--base-url', LOCAL_API_URL,
+75 -13
View File
@@ -1,23 +1,85 @@
import { readAppFile } from '../test-support/i18n.js';
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 root = path.resolve(import.meta.dirname, '..');
const script = path.resolve(import.meta.dirname, '../scripts/upload-presentation-route.sh');
async function read(relativePath) {
return readAppFile(relativePath);
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 example uses real GPX Streckenpunkte and the existing upload API', async () => {
const gpx = await read('examples/presentation-route.gpx');
const metadata = JSON.parse(await read('examples/presentation-route.json'));
const script = await read('scripts/upload-presentation-route.sh');
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/);
});
assert.ok((gpx.match(/<trkpt\b/g) || []).length >= 8);
assert.doesNotMatch(gpx, /<rtept\b/);
assert.ok(metadata.pois.length >= 3);
assert.match(script, /POST "\$BASE_URL\/routes"/);
assert.match(script, /POST "\$BASE_URL\/routes\/\$ROUTE_ID\/pois"/);
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'));
});